一.系统调用
什么是系统调用?
只有系统调用(系统函数)才能进入内核空间,库函数也是调用系统函数,才得以访问底层。
系统调用由操作系统实现并提供给外部应用程序的编程接口。是应用程序同系统之间数据交互的桥梁。
换句话说,系统调用就是操作系统封装好的函数接口,使得用户能够利用它调用底层驱动,从而控制硬件。
二.open函数,read函数,write函数
1.open
所需头文件:
#include<fcntl.h>
#include<unistd.h>
open函数用来打开文件,具体有以下两种重载类型。
正常打开返回一个int整数(文件描述符),打开出错返回-1
path : 路径名 flags: 打开方式 mode:生成文件时的权限(4位8进制数)
第一种是文件存在时,指定打开方式
第二种是文件不存在,且要求创建文件时,按mode指定权限
flags分为以下几种:
O_RDONLY :只读
O_WRONLY:只写
O_RDWR:读写
O _APPEND:追加写
O_CREAT:无文件时创建
O_EXCL:是否存在
O_TRUNC:文件清空再打开
O_NONBLOCK:以非阻塞方式打开(使用在设备,网络文件下)
#include<fcntl.h> 这些包含在头文件<fcntl.h>中,所以需要引入#include<fcntl.h>
使用close(文件描述符) ----------关闭流
2.read
read函数返回读入的字节数,读出错误返回-1
read(文件描述符,字符串缓冲区,读出字节数)
3.write
write函数将缓冲区内容读入文件,返回写入的字节数,写入失败返回-1
write(文件描述符,字符串缓冲区,写入字节数)
4.案例:使用read write实现cp操作
cp 文件1 文件2
对main函数传递两个参数,代表文件名
文件一,使用只读打开方式
文件二,无文件时创建文件。
使用buffer缓冲区(1024字节),循环输入文件,当read函数读入字符数为空时,read函数返回0,此时退出循环。
二.预读入缓输出
库函数和系统调用执行的速度,一定是系统调用速度更快吗?
答案是不一定,如fputc库函数与write/read 系统函数,若将缓冲区buffer设置为1,使它们每次都读入一个字节,谁的速度更快?
首先明确,由于向磁盘写入数据较慢,在内核区中会设置一个缓冲区,直到缓冲区有足够数据时再一次性写入磁盘。在用户区中,也会有一个缓冲区,它存在的目的是希望数据暂时读入到缓冲区里,然后再一次性输入到内核区的缓冲区里。这是因为从用户区到内核区是需要时间开销的,通过预读入就可以避免多次用户/内核区之间的跳转。
所以,若设置系统调用write/read的自定义缓冲区为1个字节,那么每读入一个字节,就要执行一次从用户区到内核区的跳转,时间开销大。
但是库函数fputc开的缓冲区较大(默认4096字节),先在用户区中预读入,然后再一次性执行系统调用,就避免了大量的时间开销,所以,虽然每次都是读入一个字节,底层实现原理是预读入缓输出,这跟开的缓冲区大小有关。
三.文件描述符
一个进程在内存中有自己的内存地址空间,其中,PCB(进程控制块)被存放在内存中,用来管理该进程,PCB本质上是一种数据结构,维护着进程的一些信息,其中就包含了文件描述符表。
PCB维护了一个指向该表的指针,通过这个指针可以访问到文件描述符表,文件描述表中的每一个节点,都保存了一个指针,指向某个文件结构体。我们上文所说的文件描述符,实际上是它的下标。
默认最大保存文件数:1024
一个进程中运行时会有0:标准输入文件 1:标准输出文件 2:标准错误文件 ,这也解释了为什么open一个文件时,会从3号开始返回。
0:STDIN_FILENO
1:STDOUT_FILENO
2:STDERR_FILENO
四.阻塞和非阻塞
常规文件不会产生阻塞等待,只有在打开设备文件,网络文件时才会产生阻塞。
例如标准输入文件STDIN_FILENO
默认打开设备/网络文件是以阻塞方式打开的,可以在open时使用O_NONBLOCK以非阻塞方式打开。
当文件再以非阻塞方式打开式,且无数据输入,read返回-1,且errno = EAGAIN 或者EWOULDBLOCK,此时并不是说读文件错误,而是说明文件可打开,但是没有数据输入。
如下,处理标准输入
五.fcntl函数修改文件访问属性
fcntl可以不重新打开,就可以修改文件访问属性
获取当前访问属性: fcntl(“文件描述符”,F_GETFL),返回一个int类型,是一个位图,表示当前访问属性
对于该返回值,设其为flgs, 可以通过 | 为其添加属性---------- flgs |= O_XXXXX
设置属性:fcntl(“文件描述符”,F_SETFL,flgs) ,