1、系统调用 open
的概念
函数原型:int open(const char* pathname, int flags, mode_t mode)
const char* pathname
:文件路径(和fopen
一样)flag
:标志位mode
:默认权限
flags
:标志位
以下是一些常用的 flags
标志及其含义:
- O_RDONLY: 只读模式打开文件。
- O_WRONLY: 只写模式打开文件。
- O_RDWR: 读写模式打开文件。
- O_CREAT: 如果文件不存在,则创建该文件。需要提供
mode
参数。 - O_EXCL: 与
O_CREAT
一起使用,如果文件已经存在,则open
调用失败并返回-1
。 - O_TRUNC: 如果文件已经存在且是一个普通文件,则将其长度截断为0。
- O_APPEND: 每次写操作前将文件偏移量设置为文件末尾,即追加写入。
标志位 flags
的作用类似于 库函数 fopen
中的选项(如 w
/ r
)
例如:库函数 fopen("r")
表示打开文件用于 读 ,则 系统调用 open(O_RDONLY)
表示打开文件用于 读
这些大写的标志位实际上其实是一个个宏,这些标志位可以组合使用,如 O_RDONLY | O_WRONLY
为什么说可以组合使用,因为标志位 flags
就是位图!
flags
位图:使用位图组合多个flag
传参
每个flags
标志,如 O_RDONLY、O_WRONLY
实际上是一个只有一个比特位为 1 的整数值,例如:
#define O_RDONLY 00000000 // 仅读模式
#define O_WRONLY 00000001 // 仅写模式
#define O_RDWR 00000010 // 读写模式
#define O_CREAT 00000100 // 如果文件不存在,则创建文件
#define O_TRUNC 00001000 // 如果文件已存在,则将其长度截断为 0
这些标志通常通过按位或(OR)运算符 |
组合在一起。例如:
int flags = O_WRONLY | O_CREAT | O_TRUNC;
在这个例子中,flags
的值将是 00000100 | 00001000 | 00000001
,即 00001101
。每个标志的比特位互不冲突,因此可以安全地组合在一起。
这就是位图的作用,只需传递一个这样的位图 int flags
,通过每个比特位来标记某个参数是否存在(被传递),解决了一次性不能同时传递多个参数的问题!
本例中 int flags = 00001101
表示此处传递三个标志:O_WRONLY、O_CREAT、O_TRUNC
很多系统调用其实都是通过这种方式,实现多个宏选项的组合传参
代码演示:用位图组合传参,函数如何处理
我们定义几个宏,每个宏都使用位运算,将 1 位移动到不同位置的比特位,代表不同的宏
在 main
函数中,我们通过调用 PrintTest
,每次通过按位或运算(有一留一,其他为零)传入不同的标志组合。
在 PrintTest
函数中,通过将 flags
和 不同的宏进行按位与运算(同一为一,其他为零),就能判断对应哪个宏,则进入不同的 if 语句中打印不同的内容。
#include<stdio.h>
#define ONE (1 << 0)
#define TWO (1 << 1)
#define THREE (1 << 2)
#define FOUR (1 << 3)
#define FIVE (1 << 4)
void PrintTest(int flags)
{
if (flags & ONE)
{
printf("one\n");
}
if (flags & TWO)
{
printf("two\n");
}
if (flags & THREE)
{
printf("three\n");
}
if (flags & FOUR)
{
printf("four\n");
}
if (flags & FIVE)
{
printf("five\n");
}
}
int main()
{
printf("==================\n");
PrintTest(ONE);
printf("==================\n");
PrintTest(TWO);
printf("==================\n");
PrintTest(THREE);
printf("==================\n");
PrintTest(ONE | THREE);
printf("==================\n");
PrintTest(ONE | TWO | THREE);
printf("==================\n");
PrintTest(ONE | TWO | THREE | FOUR);
printf("==================\n");
return 0;
}
2、系统调用 open
的使用
标志位 flags
的设置
前面讲解过库函数 fopen("w")
表示若文件存在则写入文件,不存在则先新建文件
这里涉及两个逻辑步骤:1、会新建文件;2、再写入
对于 系统调用 open
的写操作 O_WRONLY
,仅仅只会写入文件,并不会做前置准备(如检测文件不存在就新建)
系统调用 open
的各种操作实际上偏向底层较为直接的方式, 而库函数 fopen
会做充足准备后再操作。
因此系统调用open
需要组合 写操作和创造操作 两个组合使用
O_CREAT
:检测文件是否存在,若不存在则新建
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
open("log.txt", O_WRONLY | O_CREAT);
return 0;
}
该代码运行结果如下:
可以发现,这个新建的 log.txt
文件显示的权限设置中,为什么有个权限 s
?因为权限只有 rwx
三种,这个实际上是权限被设置成了乱码!
这提示我们,当使用系统调用 open
创建文件时,还需要自己设置新建文件的默认权限!
默认权限 mode
的设置
这里就要使用到第三个参数 mode
:
int open(const char* pathname, int flags, mode_t mode)
示例代码:
若想要将新建文件的默认权限设置为 rw-rw-rw-
则需要将 mode
设置成 rw-rw-rw-
,即八进制表示的默认权限为 0666(注意不是 666,C 语言中,前缀 0
用于表示一个数是八进制数,若不加上前缀 0,则会导致识别成十进制,则权限会被设置成乱码)
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
open("log.txt", O_WRONLY | O_CREAT, 0666);
return 0;
}
该代码运行结果如下:
可是我们发现,最终设置出来的文件权限并非我们目标的 rw-rw-rw-
,而是 rw-rw-r--
这是因为在 Unix 和类 Unix 系统中,文件的最终权限是由权限掩码(umask)和默认权限共同决定的。
我们还需要自定义设置权限掩码,否则系统会使用系统默认权限掩码
权限掩码的设置
最终权限的计算公式:最终权限 = 默认权限 & (~umask)
命令 umask
可以查询当前系统默认的权限掩码:
我们第(2)点中得出的最终权限 rw-rw-r--
= 0664
= 0666
& (~0002)
因此可以证明为什么最终权限是 rw-rw-r--
我们也可以自己设置权限掩码:umask()
函数原型:
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
参数解释:
mask
:新的文件创建掩码,一个八进制数
设置成 0,则最终权限就是 0666
(即我们所期待的结果)
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
open("log.txt", O_WRONLY | O_CREAT, 0666);
return 0;
}
该代码运行结果如下:
自己设置的权限掩码会覆盖系统的吗?并不会
若没有自己设置则用系统默认的,有就用你设置的:当你没有设置权限掩码时,默认使用系统的权限掩码;当你自定义设置权限掩码时,会先选择你自己的。
设计自己的 touch
命令
上面文章我们学习了如何使用系统调用创建文件,并设置默认权限与权限掩码,我们就可以自定义实现一个 touch
命令
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc, char* argv[])
{
if(argc == 2)
{
umask(0);
open(argv[1], O_CREAT, 0666);
}
return 0;
}
该代码运行结果如下:
如下使用自定义的 “mytouch
” 命令创建了新文件 newlog.txt