合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
[TOC] # 简介 从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。 ![](https://img.kancloud.cn/04/85/04854b26517623a280b2ece45e4aa358_227x226.png) 系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对系统进行“保护”,因为我们知道 Linux 的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离。 换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全无误 ## 系统调用的实现 系统调用是属于操作系统内核的一部分的,必须以某种方式提供给进程让它们去调用。 CPU 可以在不同的特权级别下运行,而相应的操作系统也有不同的运行级别,**用户态和内核态**。 运行在内核态的进程可以毫无限制的访问各种资源,而在用户态下的用户进程的各种操作都有着限制,比如不能随意的访问内存、不能开闭中断以及切换运行的特权级别。显然,属于内核的系统调用一定是运行在内核态下,但是如何切换到内核态呢? 答案是软件中断。软件中断和我们常说的中断(硬件中断)不同之处在于,它是通过软件指令触发而并非外设引发的中断,也就是说,又是编程人员开发出的一种异常(该异常为正常的异常)。 **操作系统一般是通过软件中断从用户态切换到内核态** ## 系统调用和库函数的区别 Linux 下对文件操作有两种方式:**系统调用(system call)**和**库函数调用(Library functions)**。 库函数由两类函数组成: 1. 不需要调用系统调用 不需要切换到内核空间即可完成函数全部功能,并且将结果反馈给应用程序,如strcpy、bzero 等字符串操作函数。 2. 需要调用系统调用 需要切换到内核空间,这类函数通过封装系统调用去实现相应功能,如 printf、fread等。 ![](https://img.kancloud.cn/33/46/334657f3edd4ade4a8a47512b313507f_402x420.png) 系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时,CPU工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时间 # C库中IO函数工作流程 ![](https://img.kancloud.cn/4c/54/4c5406db765be9923ca581bb27efcc53_840x436.png) 库函数访问文件的时候根据需要,设置不同类型的缓冲区,从而减少了直接调用 IO 系统调用的次数,提高了访问效率。 这个过程类似于快递员给某个区域(内核空间)送快递一样,快递员有两种方式送: 1. 来一件快递就马上送到目的地,来一件送一件,这样导致来回走比较频繁(系统调用) 2. 等快递攒着差不多后(缓冲区),才一次性送到目的地(库函数调用) # 错误处理函数 errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。查看错误代码errno是调试程序的一个重要方法。 当Linux C api函数发生异常时,一般会将errno全局变量赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。 ~~~ #include <stdio.h> //fopen #include <errno.h> //errno #include <string.h> //strerror(errno) ​ int main() { FILE *fp = fopen("xxxx", "r"); if (NULL == fp) { printf("%d\n", errno); //打印错误码 printf("%s\n", strerror(errno)); //把errno的数字转换成相应的文字 perror("fopen err"); //打印错误原因的字符串 } ​ return 0; } ~~~ 查看错误号: > /usr/include/asm-generic/errno-base.h > > /usr/include/asm-generic/errno.h # 虚拟地址空间 每个进程都会分配虚拟地址空间,在32位机器上,该地址空间为4G 。 ![](https://img.kancloud.cn/83/00/83001929888221b4a4e8e0cc5818c549_888x625.png) # 文件描述符 打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。 程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的 ~~~ #define STDIN_FILENO 0 //标准输入的文件描述符 #define STDOUT_FILENO 1 //标准输出的文件描述符 #define STDERR_FILENO 2 //标准错误的文件描述符 ~~~ 在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中. ![](https://img.kancloud.cn/16/6a/166a8de0e72df3ba8c261745fa095c2f_866x588.png) **最大打开的文件个数** Linux 中一个进程最多只能打开 NR\_OPEN\_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。 * 查看当前系统允许打开最大文件个数: > cat /proc/sys/fs/file-max * 当前默认设置最大打开文件个数1024 > ulimit -a * 修改默认设置最大打开文件个数为4096 > ulimit -n 4096