我们可以用系统调用来操作文件,这种方式和I/O库函数各有千秋,我们需要明白库函数在用户地址空间执行,系统调用是在内核地址空间执行,依赖Linux系统,不要搞混了,那我们下面来学习一下。
文章目录:
一、基本概念
(一)文件描述符
系统调用是在内核空间执行的,那么我们就需要了解在内核中是如何标识文件的。对于内核而言,所有打开的文件都通过文件描述符(简称fd)引用就是标识。文件描述符是一个非负整数,指代被打开的文件,当打开一个现有的文件或创建一个新文件时,内核向进程返回一个文件描述符,将其作为参数给系统调用的I/O操作。
POSIX标准要求每次打开文件时,必须从小到大申请文件描述符,那么最小的文件描述符是几呢?不是0,因为系统已经规定了前三个:
文件描述符 | POSIX名称 (常量定义在头文件<unistd.h>) | 用途 | stdio流 |
---|---|---|---|
0 | STDIN_FILENO | 标准输入 | stdin |
1 | STDOUT_FILENO | 标准输出 | stdout |
2 | STDERR_FILENO | 标准错误 | stderr |
所以 最小的文件描述符从3开始,即打开第一个文件open()返回的文件描述就是3。
那么 系统创建文件的上限是多少呢?我们可以使用指令来查看最多可以打开多少个文件:
sysctl -a | grep fs.file-max
同时,内核为了防止一个进程创建多个文件,占用文件描述符,对单个进程创建文件的上限也做了规定,可以用:
ulimit -n
默认值为1024个。文件创建的上限是可以更改,在这里我就不阐述了,有兴趣的可以去了解。
(二)带缓冲区的I/O && 不带缓冲区的I/O
带缓冲区I/O就是在内存开辟缓冲区,当执行读文件操作时,先把磁盘文件读到缓冲区,进行一次读取,再根据程序需要将数据给读给变量。我们上次学的库文件I/O函数就是带缓冲区的,如fopen,fread等。这种特性的好处是和外存的交互次数由缓冲区大小决定,如果缓冲区大,和对外存操作少。如果进入内核读取数据,那么交互次数会少,因为它会尽可能地一次读取数据。故执行速度就快,效率高。
不带缓冲区的I/O就是执行I/O函数时,根据程序需求,直接将数据给变量。不带缓冲区I/O依赖于操作系统,即系统级输入输出,就是我们今天要学习的系统调用I/O,如open等,它会和内核进行多次交互,所以执行速度会慢。
但是我们不能以偏概全,不带缓冲区地read系统调用函数一定比带缓冲区地fread库函数速度慢,这时不对的,只有合适,没有不好。我们知道read需要进行多次内核态和用户态的交互,而fread则交互少,如果我们是顺序读取数据,那么fread即带缓冲区的更快,但是如果我们随机读取数据,这时缓冲区的作用降低,read反而速度快。所以要在一定的情景下来找最合适的。
(三)man指令
介绍关于man的指令,方便查看函数的信息:
指令 | 含义 |
---|---|
man 1 command | 查看命令的帮助手册 |
man 2 系统调用 | 查看系统调用手册 |
man 3 库函数 | 查看库函数的使用手册 |
(四)基础中断机制知识
系统调用是由内核提供的一系列函数,实现时涉及到从用户态进入内核态,那么我们如何进入呢?中断,中断是现代计算机不可或缺的机制。
中断的概念: 中断是指CPU在正常运行程序时,由于内部/外部事件或由程序预先安排引起CPU暂停正在运行的程序,转到引起中断的事件程序中去,处理完毕,再返回到继续执行被暂停的程序。
中断的分类:
-
由CPU外部引起的,称为中断,如I/O中断等。
-
由CPU内部事件或程序引起的,称作异常,如数组非法越界等。
-
由在程序中使用了系统调用而引发的过程,称作陷入。
前两个都是被动的,最后一个是主动的,所以把最后一个也称为软中断。
操作系统处理中断: 操作系统中有一张中断表,称为中断描述符表,它保存着中断向量号(即中断序号),对应的异常事件,需要调用的处理程序这三个信息。硬件中断机制提供了256个入口,即包含了256种中断事件类型,它规定:
- 0~31号中断向量被Intel公司保留用来处理异常事件,即操作系统提供对应的异常处理程序,产生一个异常时,找到向量号,调用运行处理程序,处理完成,返回中断点。但是在2.2.5版本的Linux只提供了0~17号中断向量的处理程序,包括了溢出,越界等。
- 0x80(SYSCALL_VECTOR)规定用作系