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手册使用在前面文章中有详细讲解,可通过往期文章查看学习。
数据类型为 const char* 可以填写字符串的字面常量(指直接用 " " 引起来的字符串),这个路径可以填绝对路径和名称。直接填名称会自动检索当前目录下的文件。
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 所指的文件为一符号连接, 则会令打开文件失败.
不可以同时使用,也不能添加 | (本质上都占用了两个同样的数字位),其余的旗标是可以加 | 的(占用不同的数字位)。
例如:8bit控制位,控制读写的在第1,2bit,xxxxxx00表示读数据,xxxxxx01表示写数据,xxxxxx11表示读写。这时O_RDONLY | O_WRONLY (00 | 01)还是只能得到数字01(O_WRONLY),对这三个旗标用 | 进行操作意义不大。
调用 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报错。
写入文件时,系统默认将光标移动到文件开头。
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"结果如下:
注:这里打开的时候,将光标移动到末尾进行写入,不破坏原来文件的内容。
在文件处于阻塞状态时强行打开文件。
例如我写了个只有从串口接收到数据才能关闭并且保存文件的程序,如果程序迟迟没有从串口中接收到数据,那么文件就会一直处于阻塞状态中等待数据,这个时候文件无法正常打开,需要添加此旗标。
暂无代码验证...
在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()函数更加“底层”一些。