概述
UNIX环境高级编程第三章--文件IO
- 内容概述
- 一 基础
- C标准函数与系统函数的区别
- 文件描述符
- perror和errno
- 二 open函数
- 三 creat函数
- 四 close函数
- 五 lseek函数
- 六 read/write函数
- read函数
- write函数
- 代码展示
- /IO函数测试--->open close read write lseek
- 使用lseek函数获取文件大小
- 七 dup、dup2、fcntl函数
- 文件共享机制
- dup函数
- dup2函数
- 测试dup2函数
- fcntl函数
- 测试代码
- 八 同步写与延迟写
- 函数syns、fsyns、fdatasyns
内容概述
- open,creat,lseek,read,write函数的使用
- dup,dup2,fcntl函数的使用
- 其他
一 基础
C标准函数与系统函数的区别
- 系统调用
由操作系统实现并提供给外部应用程序的编程接口。(Application Programming Interface,API)。是应用程序同系统之间数据交互的桥梁。
要区分系统调用和库函数,虽然两者都是函数,但是一般系统调用是原子操作,而库函数一般都是由系统提供的API实现的。
文件描述符
- 当打开一个现有文件或者是创建一个新的文件时,内核向进程返回一个文件描述符。
- shell将0,1,2占用了,分别对应标准输入,标准输出和标准错误输出。但是编程中应当用STDIN_FILENO,STDOUT_FILENO,STDERR_FIELNO来替代。这些常量在头文件<unistd.h>。
perror和errno
- errno是一个全局变量, 当系统调用后若出错会将errno进行设置, perror可以将errno对应的描述信息打印出来.
如:perror(“open”); 如果报错的话打印: open:(空格)错误信息
二 open函数
函数描述: 打开或者新建一个文件
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数参数:
- pathname参数是要打开或创建的文件名,和fopen一样, pathname既可以是相对路径也可以是绝对路径。
- flags参数有一系列常数值可供选择, 可以同时选择多个常数用按位或运算符连接起来, 所以这些常数的宏定义都以O_开头,表示or。这些常量值在<fcntl.h>中定义。
- 必选项:以下三个常数中必须指定一个, 且仅允许指定一个。
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 可读可写打开
- 以下可选项可以同时指定0个或多个, 和必选项按位或起来作为flags参数。可选项有很多, 这里只介绍几个常用选项:
- O_APPEND 表示追加。如果文件已有内容, 这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
- O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode, 表示该文件的访问权限。
• 文件最终权限:mode & ~umask - O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
- O_TRUNC 如果文件已存在, 将其长度截断为为0字节。
- O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O),非阻塞I/O。
- 必选项:以下三个常数中必须指定一个, 且仅允许指定一个。
函数返回值:
- 成功: 返回一个最小且未被占用的文件描述符
- 失败: 返回-1, 并设置errno值.
三 creat函数
函数原型:
#include<fcntl.h>
int creat(const char * path,mode_t mode)
实际上相当于:
open(path,O_EDWR|Q_CREAT|O_TRUNC,mode)
所以尽量用open(),不用creat()。
四 close函数
- 原型
#include<unistd.h>
int close(int fd); - 当一个进程终止时,内核自动关闭它的所有打开的文件,所以一般都利用这一特性而不显式的用close关闭文件。
五 lseek函数
介绍
- 所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为cfo. cfo通常是一个非负整数, 用于表明文件开始处到文件当前位置的字节数.
- 读写操作通常开始于 cfo, 并且使 cfo 增大, 增量为读写的字节数. 文件被打开时, cfo 会被初始化为 0, 除非使用了 O_APPEND.使用 lseek 函数可以改变文件的 cfo
函数原型
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数解析
- 函数描述: 移动文件指针
- 函数原型: off_t lseek(int fd, off_t offset, int whence);
- 函数参数:
- fd:文件描述符
- 参数 offset 的含义取决于参数 whence:
1 如果 whence 是 SEEK_SET,文件偏移量将设置为 offset。
2 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
3 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
- 函数返回值: 若lseek成功执行, 则返回新的偏移量。
- lseek函数常用操作
- 文件指针移动到头部
lseek(fd, 0, SEEK_SET); - 获取文件指针当前位置
int len = lseek(fd, 0, SEEK_CUR); - 获取文件长度
int len = lseek(fd, 0, SEEK_END); - lseek实现文件拓展
off_t currpos;
// 从文件尾部开始向后拓展1000个字节
currpos = lseek(fd, 1000, SEEK_END);
// 额外执行一次写操作,否则文件无法完成拓展
write(fd, “a”, 1); // 数据随便写
- 文件指针移动到头部
六 read/write函数
read函数
- 函数描述:从打开的设备或文件中读取数据
- 函数原型:ssize_t read(int fd, void *buf, size_t count);
- 函数参数:
- fd:文件描述符
- buf:读上来的数据保存的缓冲区
- count:buf缓冲区存放的最大字节数
- 函数返回值:
- 大于0:读到的字节数
- =0:以达到文件尾
- <0 : 出错,并设置errno
【注】count也可以理解为想要读取的字节数,返回值(实际读到的字节数)不一定等于count,原因有很多种,最常见的是,在读到要求字节数之前到达了文件尾端。
write函数
- 函数描述 向打开的设备或文件中写数据
- ** 函数原型** ssize_t write(int fd, const void *buf, size_t count);
- 函数参数:
- fd:文件描述符
- buf:缓冲区,写入文件或设备的数据
- count:buf中数据的长度/或者可以说是从buf中写入文件的字节数
- 函数返回值
- 成功:返回写入的字节数
- 失败:返回-1,并设置errno
【注】1. 返回值通常与参数count的值相同,否则表示出错。
2. 对于普通文件,写操作从文件的当前偏移量开始。如果在打开该文件的时候指定了O_APPEND选项,则在每次写操作之前,将文件的偏移梁设置在文件的当前结尾处。在一次写成功之后,该文件偏移量增加实际写的字节数。
代码展示
/IO函数测试—>open close read write lseek
//IO函数测试--->open close read write lseek
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//打开文件
int fd = open(argv[1], O_RDWR | O_CREAT, 0777);
if(fd<0)
{
perror("open error");
return -1;
}
//写文件
//ssize_t write(int fd, const void *buf, size_t count);
write(fd, "hello world", strlen("hello world"));
//移动文件指针到文件开始处
//off_t lseek(int fd, off_t offset, int whence);
lseek(fd, 0, SEEK_SET);
//读文件
//ssize_t read(int fd, void *buf, size_t count);
char buf[1024];
memset(buf, 0x00, sizeof(buf));
int n = read(fd, buf, sizeof(buf));
printf("n==[%d], buf==[%s]n", n, buf);
//关闭文件
close(fd);
return 0;
}
使用lseek函数获取文件大小
//lseek函数获取文件大小
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//打开文件
int fd = open(argv[1], O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
//调用lseek函数获取文件大小
int len = lseek(fd, 0, SEEK_END);
printf("file size:[%d]n", len);
//关闭文件
close(fd);
return 0;
}
七 dup、dup2、fcntl函数
文件共享机制
内核使用三种数据结构表示打开文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程肯呢个产生的影响:
(其中的文件状态标志可以理解为O_RDONLY,O_WRRD等文打开的时候的常量参数)
如果两个独立的进程各自打开了同一文件,则有下图的关系:
我们假定第一个进程在文件描述符3上打开该文件,而另一个进程在文件描述符4上打开该文件,打开该文件的每一个进程都获得各自的一个文件表项,但对一个给定的文件只有一个V节点表项。之所以每个进程都获得自己的文件表项,是因为这可以使每个进程都有它自己的对文件的当前偏移量。
- 在完成每个write后,在文件表项中的当前文件偏移量即增加所写入的字节数。如果这导致当前文件偏移量超出了当前文件长度则将i节点表项中的当前文件长度设置为当前文件偏移量(也就是文件加长了)。
- 如果用O_APPEND标志打开一个文件,则相应状态标志也被设置到文件表项的文件状态标志中。每次对这种具有追加写标志的文件执行写操作时,文件表项中的当前文件偏移量首先会被设置为i节点表项中的文件长度。这就是的每次写入的数据都追加到文件的当前尾端处。
- 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件从长度(这与用O_APPEND是不同的)。
- lseek函数只修改文件表项的当前文件偏移量,不进行任何IO操作。
- 可能有多个文件描述符指向同一文件表项。比如dup、fork等。
dup函数
- 函数描述 :复制文件描述符
- 函数原型:int dup(int oldfd);
- 函数参数:oldfd -要复制的文件描述符
- 函数返回值:
- 成功: 返回最小且没被占用的文件描述符
- 失败: 返回-1, 设置errno值
若进程启动时执行了newfd=dup(1);当此函数执行时,假定下一个可以使用的文件描述符是3(这是非常可能的,因为1,2,0都被shell默认打开)。因为两个描述符指向同一文件表项,所以他们共享同一文件状态标志(读、写、读写、朱追加等)以及同一当前文件偏移量。
### 测试dup函数
//测试dup函数复制文件描述符
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//打开文件
int fd = open(argv[1], O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
//调用dup函数复制fd
int newfd = dup(fd);
printf("newfd:[%d], fd:[%d]n", newfd, fd);
//使用fd对文件进行写操作
write(fd, "hello world", strlen("hello world"));
//调用lseek函数移动文件指针到开始处
lseek(fd, 0, SEEK_SET);
//使用newfd读文件
char buf[64];
memset(buf, 0x00, sizeof(buf));
int n = read(newfd, buf, sizeof(buf));
printf("read over: n==[%d], buf==[%s]n", n, buf);
//关闭文件
close(fd);
close(newfd);
return 0;
}
dup2函数
- 函数描述:复制文件描述符
- 函数原型:int dup2(int oldfd, int newfd);
- 函数参数:
- oldfd-原来的文件描述符
- newfd-复制成的新的文件描述符
- 函数返回值:
- 成功: 将oldfd复制给newfd, 两个文件描述符指向同一个文件
- 失败: 返回-1, 设置errno值
【注】1. 若newfd已经指向了一个文件,首先close原来打开的文件,然后newfd指向oldfd指向的文件.
2. 若newfd没有被占用,newfd指向oldfd指向的文件.
测试dup2函数
//测试dup2函数复制文件描述符
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//打开文件
int oldfd = open(argv[1], O_RDWR | O_CREAT, 0755);
if(oldfd<0)
{
perror("open error");
return -1;
}
int newfd = open(argv[2], O_RDWR | O_CREAT, 0755);
if(newfd<0)
{
perror("open error");
return -1;
}
//调用dup2函数复制fd
dup2(oldfd, newfd);
printf("newfd:[%d], oldfd:[%d]n", newfd, oldfd);
//使用fd对文件进行写操作
write(newfd, "hello world", strlen("hello world"));
//调用lseek函数移动文件指针到开始处
lseek(newfd, 0, SEEK_SET);
//使用newfd读文件
char buf[64];
memset(buf, 0x00, sizeof(buf));
int n = read(oldfd, buf, sizeof(buf));
printf("read over: n==[%d], buf==[%s]n", n, buf);
//关闭文件
close(oldfd);
close(newfd);
return 0;
}
fcntl函数
-
函数描述 :改变已经打开的文件的属性
-
函数原型:int fcntl(int fd, int cmd, … /* arg */ );
-
函数参数与功能:
- 复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
- 获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD)
- 获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)
在说明open函数时,已经描述了文件状态标志,如下图所示:
-
函数返回值:
返回值取决于cmd。- 成功:
cmd为F_DUPFD,返回一个新的文件描述符
cmd为F_GETFL,返回文件状态标志
cmd为F_SETFL,返回0 - 失败:返回-1,并设置errno。
- 成功:
fcntl函数常用操作
- 复制一个新的文件描述符
int newfd = fcntl(fd, F_DUPFD, 0); - 获取文件的状态标志
int flag = fcntl(fd, F_GETFL, 0); - 设置文件状态标志
flag = flag | O_APPEND;
fcntl(fd, F_SETFL, flag)
测试代码
//修改文件描述符的flag属性
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//打开文件
int fd = open(argv[1], O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
//获得和设置fd的flags属性
int flags = fcntl(fd, F_GETFL, 0);
flags = flags | O_APPEND;
fcntl(fd, F_SETFL, flags);
//写文件
write(fd, "hello world", strlen("hello world"));
//关闭文件
close(fd);
return 0;
}
八 同步写与延迟写
函数syns、fsyns、fdatasyns
- read、write等函数都在内核中执行,所以这些函数为不带缓冲的IO函数。
- 当我们使用write向文件写入数据时,内核通常先将数据复制到缓冲区,然后排入队列,晚些时候再写入磁盘。这种方式成为延迟写。
- 可以使用sync、fsyns、和fdatasync三个函数实现将所有延迟写数据块写入磁盘。从而保证磁盘上的实际文件系统与缓冲区保持一致。
- 打开文件时,设置文件的状态标志O_SYNC,则开启了同步写标志。当write写入磁盘时才返回。一般来讲,使用同步写会增加系统时间和时钟时间。
最后
以上就是高挑老虎为你收集整理的UNIX环境高级编程——第三章文件IO内容概述一 基础二 open函数三 creat函数四 close函数五 lseek函数六 read/write函数七 dup、dup2、fcntl函数八 同步写与延迟写的全部内容,希望文章能够帮你解决UNIX环境高级编程——第三章文件IO内容概述一 基础二 open函数三 creat函数四 close函数五 lseek函数六 read/write函数七 dup、dup2、fcntl函数八 同步写与延迟写所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复