一、系统调用
系统调用表现出来的形式和库函数看着是一样的,但是系统调用的实现是在内核中,一旦执行系统调用以后,会产生中断,陷入内核,内核去执行相应的代码。我们无法直接去执行内核的代码,系统调用执行以后会从用户态切换到内核态,内核帮我们去执行某一段代码。
系统调用与库函数的区别:
系统调用的实现在内核中,属于内核空间,库函数的实现在函数库中,属于用
户空间。
系统调用执行过程如下图:
二、操作文件的系统调用
文件描述符:文件描述符是一个整型,其实就是内核中打开的一个文件的id。
对于进程来讲,启动一个进程会有3个文件会被默认打开,这三个文件分别是标准输入,标准输出,标准错误输出文件,这3个打开文件在PCB中记录着,PCB中有一个文件表,文件表的下标为0代表第一个文件,即标准输入文件,下标为1代表第二个文件,即标准输出文件,下标为2代表第三个文件,即标准错误输出文件。在C语言中用stdin操作标准输入文件,用stdout操作标准输出文件,用stderr操作标准错误输出文件。在Linux中则用0,1,2来分别操作标准输入,标准输出,标准错误输出文件,也就是说返回值不是FILE*,而是文件描述符。
对文件的操作一般分为3步:
(1)打开文件
(2)读/写
(3)关闭文件
文件一般存放在磁盘,可以永久存储。
文件由内核来管理,内核有一个模块就是文件系统。
- open系统调用
(1)语法形式:
①用于打开一个已存在的文件
int open(const char* pathname, int flags);
②用于新建一个文件,并设置访问权限
int open(const char* pathname, int flags,mode_t mode);
(2)参数解释:
①返回值:为文件描述符
②pathname
:将要打开的文件路径和名称
③flags
: 打开方式
如 O_WRONLY 只写打开
O_RDONLY 只读打开
O_RDWR 读写方式打开
O_CREAT 文件不存在则创建
O_APPEND 文件末尾追加
O_TRUNC 清空文件,重新写入
open调用还可以在flags参数中将上述可选的打开方式用“按位或”操作进行组合。
④mode
: 权限。如:“0600”
- write系统调用
(1)语法形式:
ssize_t write(int fd, const void* buf,size_t count);
(2)参数解释:
①fd
:对应打开的文件描述符
②buf
:存放待写入的数据
③count
:计划一次向文件中写多少数据
- close系统调用
(1)语法形式:
int close(int fd);
(2)参数解释:
fd
:要关闭的文件描述符
- read文件调用
(1)语法形式:
ssize_t read(int fd, void* buf, size_t count);
(2)参数解释:
①返回值:为实际读到的字节数
②fd
:对应打开的文件描述符(读哪个文件)
③buf
:存放数据的空间(读到的内容放到哪里)
④count
:计划一次从文件中读多少字节数据 (期望读多少数据)
这四个方法是系统调用,是内核提供的接口。
【例】open系统调用,如果a.txt在当前目录已经存在,则不创建,直接以只写的方式打开,如果当前目录不存在a.txt,就创建a.txt,并以只写的方式打开。
代码如下:
编译并运行以上代码:
结果分析:
运行main程序,a.txt被创建,并将"hello"写入了a.txt。同时我们也可以看到a.txt的文件描述符为3,这是因为文件描述符0,1,2分别表示了操作标准输入,标准输出,标准错误输出文件,已经被占用了。
【注意】通过文件描述符1,也可以将所写的内容直接输出到屏幕上。
有以下代码:
编译并运行:
【例】先以只读的方式打开文件a.txt然后再读其中的内容
编译并运行以上代码:
可以看出,读到了文件a.txt中的5个字符"hello"。
如果一个文件的内容特别多,读一次读不完,就继续循环去读。执行read读文件如果读到了文件末尾是读不到数据的,当read的返回值为0时,就说明文件读完了。循环读不会在同一个地方读,执行一次read,文件中就会有一个偏移量,下一次执行read的时候就从文件偏移后的位置开始读。偏移量是会在执行了read操作之后自动往后挪的。例如:
(1)分两次去读a.txt中的内容:
编译及运行结果:
(2)当执行read将文件的内容读完之后再执行read:
编译并运行:
可以看出当执行read将文件中的内容读完之后,再执行read继续读的时候,它的返回值就变成了0,说明文件已经读完了。