open()函数是在学习文件IO中的第一个函数。作用是打开一个文件,或者创建出一个文件。

需要注意的是,能实现这样功能的函数其实有两套,一个是系统IO所提供的open()函数,一个是标准IO提供的fopen()函数。(二者对比放在最后)

先看看open函数定义:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

// 系统IO提供的函数接口

注:Linux下man手册使用在前面文章中有详细讲解,可通过往期文章查看学习。

参数解析:

pathname:英文直译是路径名称的意思。

数据类型为 const char* 可以填写字符串的字面常量(指直接用 ” ” 引起来的字符串),这个路径可以填绝对路径和名称。直接填名称会自动检索当前目录下的文件。

flags:标志(本质上是一个数字)

O_RDONLY 以只读方式打开文件 O_WRONLY 以只写方式打开文件 O_RDWR 以可读写方式打开文件. //上述三种旗标是互斥的, 也就是不可同时使用, //但可与下列的旗标利用 OR(|)运算符组合. O_CREAT 若欲打开的文件不存在则自动建立该文件. O_EXCL 如果 O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否 O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机. O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为 0, 而原来存于该文件的资 O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面 O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中 O_NDELAY 同 O_NONBLOCK. O_SYNC 以同步的方式打开文件. O_NOFOLLOW 如果参数 pathname 所指的文件为一符号连接, 则会令打开文件失败.

要点:

1.O_RDONLY,O_WRONLY,O_RDWR三个互斥:

不可以同时使用,也不能添加 | (本质上都占用了两个同样的数字位),其余的旗标是可以加 | 的(占用不同的数字位)。

例如:8bit控制位,控制读写的在第1,2bit,xxxxxx00表示读数据,xxxxxx01表示写数据,xxxxxx11表示读写。这时O_RDONLY | O_WRONLY (00 | 01)还是只能得到数字01(O_WRONLY),对这三个旗标用 | 进行操作意义不大。

2. O_CREAT | O_EXCL 常用组合作用解析。

调用 open(“abc.c”,O_CREAT|O_EXCL);

系统会自动检测当前路径下”abc.c”文件是否存在。

如果存在,就返回 -1 (文件打开错误)。

如果不存在,就自动创建文件并返回文件当前进程ID。

代码验证:

#include #include #include #include #include #include int main(){char file_name[128] = "abc.c";int fd = open(file_name,O_EXCL|O_CREAT);if(fd == -1){printf("open %s fail!",file_name);return -1;}else{printf("open %s success!",file_name);}}

代码编译后运行结果如下:

第一次运行前,目录下不存在 “abc.c” 目标文件,程序创建(O_CREAT)并打开成功。

第二次运行时,目录下存在 “abc.c” 目标文件,程序检测(O_EXCL)并返回-1报错。

3.O_TRUNC 与 O_APPEND

写入文件时,系统默认将光标移动到文件开头。

O_TRUNC:作用是在光标移动的同时将文件长度清零(清空文件)。

O_APPEND:作用是在尾部追加内容,即将光标移动到文件末尾。

代码验证:

//文件成功打开后提示打开成功并且向文件写入“12”#include #include #include #include #include #include int main(){char file_name[128] = "abc.c";int fd = open(file_name,O_RDWR);if(fd == -1){printf("open %s fail!\n",file_name);return -1;}else{printf("open %s success!\n",file_name);write(fd,"12",sizeof("12"));}}

开始时文件内容如下:

调用 open(file_name,O_RDWR)并写入”12″结果如下:

注:这里是系统默认将光标移动到开头并且写入,没有清除后面文件内容。

调用 open(file_name,O_RDWR | O_TRUNC)并写入”12″结果如下:

注:这里打开的时候,将光标移动到开头的同时将文件内容清零。

调用 open(file_name,O_RDWR | O_APPEND)并写入”12″结果如下:

注:这里打开的时候,将光标移动到末尾进行写入,不破坏原来文件的内容。

4.O_NONBLOCK解析

在文件处于阻塞状态时强行打开文件。

例如我写了个只有从串口接收到数据才能关闭并且保存文件的程序,如果程序迟迟没有从串口中接收到数据,那么文件就会一直处于阻塞状态中等待数据,这个时候文件无法正常打开,需要添加此旗标。

暂无代码验证…

其他参数讲解:

在man函数查询到的open定义中。

除了int open(const char *pathname, int flags);之外,还有一个比较特殊的函数定义。

int open(const char *pathname, int flags, mode_t mode);

这个函数和上面的对比,返回值和前两个参数都是一样的。

这里解释一下第三个参数mode:

这个函数的作用在于在创建文件的时候设定文件的初始权限。mode_t类型是一个12bit类型数据。

对此参数,官方设定了一些宏定义:

S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.
S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.
S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.
S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.
S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.
S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.
S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.
S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.
S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.
S_IROTH 00004 权限, 代表其他用户具有可读的权限
S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.
S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.

我们也可以自己写入一些参数定义它的用户权限(部分运行环境可能不行)。

需要注意的是,对于设置文件权限,并不是“写入多少就是多少”,设置权限的时候,还受到umask掩码的影响。

umask对于文件的影响:

文件最终权限 = 写入权限 – umask

代码验证:

char file_name[128] = “test”;
open(file_name,O_RDWR|O_CREAT,0727);

按以上函数创建一个文件。

此时我的umask值是 007

这时候创建文件,其权限为:

=

注意看,umask的值为007,对应test权限的低3位,这时它的低3位权限是被屏蔽掉的,哪怕我设置的初始权限为0727.

现在将我的umask值改为070:

这个时候再次以0727的权限创建文件,创建后结果如下:

此时低3位得到设置,但是中间三位就被屏蔽了。

函数返回值解释 :

open()函数的返回值是一个int,返回的是当前进程的进程ID。

注:这个ID只是当前进程下的ID。

对于返回值,我们可以对比一下 标准IO:

对于系统IO和标准IO的不同,上篇文章有提到过:

系统IO提供的接口功能单一,并且没有设置缓冲区。

而标准IO功能更加多样化,并且是带有缓冲区的。

而标准IO下的fopen()函数定义是这样的:

FILE *fopen(const char *pathname, const char *mode);

对比open():

int open(const char *pathname, int flags);

输入参数大同小异,最大的区别在于返回值。

fopen返回的是一个结构体指针,结构体指针指向FILE结构体会包含除了进程ID以外更多的文件信息。调用时也会自动开辟缓冲区,读取信息后在调用open()函数操作。

相较而言,open函数会比fopen()函数更加“底层”一些。