Linux系统调用的过程
今日小米面试问到的问题,一下子没反应过来是中断的触发与状态恢复,太菜了,只回答了一个用户态到内核态的切换。太菜了,555,复盘记录一下答案。
1.什么是系统调用
系统调用(syscall)是linux内核为用户态程序提供的主要功能接口。通过系统调用,用户态进程能够临时切换到内核态,使用内核态才能访问的硬件和资源完成特定功能。系统调用由linux内核和内核模块实现,内核在处理系统调用时还会检查系统调用请求和参数是否正确,保证对特权资源和硬件访问的正确性。通过这种方式,linux在提供内核和硬件资源访问接口的同时,保证了内核和硬件资源的使用正确性和安全性。
2.常见系统调用
根据Linux系统调用的功能,下面列举一些常用的库函数,并按照功能进行分类:
文件操作类
- fopen()、fclose():打开或关闭文件。
- fread()、fwrite():从文件读取或向文件写入数据。
- fseek():移动文件指针。
- remove()、rename():删除或重命名文件。
进程控制类
- getpid()、getppid():获取当前进程ID或其父进程ID。
- fork():创建一个新进程。
- execv()、execve():执行一个新程序。
- wait()、waitpid():等待子进程退出或捕获信号。
- exit():退出当前进程
目录和文件管理类
- opendir()、closedir():打开或关闭目录。
- readdir():读取目录中的项。
- mkdir()、rmdir():创建或删除目录。
网络相关类
- socket()、bind()、listen()、accept():创建并监听套接字,接受连接。
- connect():建立到远端主机的连接。
- read()、write():从套接字读取或写入数据。
内存管理类
- malloc()、calloc()、realloc()、free():分配或释放内存。
- mmap():将文件映射到内存中。
信号处理类
- signal()、sigaction():安装信号处理函数。
- kill():向进程发送信号。
时间和日期类
- time():获取当前时间。
- localtime()、gmtime():将时间转换为本地或UTC时间。
- strftime():将时间格式化为字符串形式。
进程间通信类
- pipe():创建管道。
- shmget()、shmat()、shmctl()、shmdt():控制共享内存。
- semget()、semop()、semctl():控制信号量。
3.系统调用基本流程
syscall接口由glibc提供和实现,第一个参数number表示需要调用的系统调用编号,后续的可变参数根据系统调用类型确定。内核具体支持的系统调用号可在<sys/syscall.h>中查看。函数调用失败会返回-1,具体错误原因保存在errno中,errno的含义可参考<errno.h>
需要注意的是,这里的返回值和errno是glibc封装提供的,内核的系统调用响应函数本身不提供errno,返回值也不同。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h> /* For SYS_xxx definitions */
int syscall(int number, ...);
Linux系统调用的基本流程大致如下所示:
- 应用程序调用系统调用函数:当应用程序需要执行一个系统调用时,它会调用相应的系统调用函数,例如open()、read()等。这些系统调用函数包含在C库中,并提供了对应的系统调用号和参数。
- 系统调用函数生成软中断:当应用程序调用系统调用函数时,相应的系统调用号和参数被传递给系统调用函数。系统调用函数会将这些参数打包成一个指令集,然后通过int 0x80、sysenter或syscall等指令生成软中断(也称为陷阱、内陷)。
- 内核处理中断请求:当应用程序生成软中断后,处理器会暂停应用程序的运行,保存用户态堆栈信息,从用户态切换到内核态,并执行中断处理程序。在Linux中,中断处理程序位于内核代码空间,并由内核管理。
- 内核处理系统调用:当内核接收到中断请求后,它会根据系统调用号和参数确定应该执行哪个系统调用,然后执行相应的系统调用处理程序。系统调用处理程序可以访问内核空间中的所有数据结构和设备。
- 内核返回结果:当系统调用处理程序完成执行后,它将结果返回给应用程序,从内核态切换到用户态,并恢复之前保存的用户态堆栈信息。如果发生错误,系统调用处理程序会返回一个错误代码。
- 应用程序继续执行:当应用程序收到系统调用函数的返回值后,它会根据返回值执行相应的操作,并继续执行程序的下一条指令。
4.举个栗子
下面以打开一个文件(面试被鞭打的题目)为例,介绍一下Linux系统调用的具体流程:
- 应用程序调用open()函数:当应用程序需要打开一个文件时,它会调用open()系统调用函数,并传递相应的参数(例如文件名、打开模式等)。
- open()函数生成软中断:当应用程序调用open()系统调用函数时,该函数会将参数打包成一个指令集,然后通过int 0x80、sysenter或syscall等指令生成软中断,并触发一次从用户态到内核态的切换。
- 内核处理中断请求:处理器在接收到软中断请求后,会进入内核态,中断服务例程会检查请求的参数和权限,并根据系统调用号分派给不同的系统调用处理程序,对于open()系统调用,则将它分发给相应的sys_open函数进行处理。
- 执行打开文件操作:当sys_open函数接收到open()系统调用请求后,它会检查参数合法性、文件是否存在等情况,如果一切正常则执行打开文件的操作,例如在内核建立一个文件结构体对象,分配文件描述符等。
- 返回结果给应用程序:当sys_open函数执行完毕后,它将文件描述符等结果返回给中断服务例程,之后中断服务例程再将结果返回给应用程序调用open()函数的那个线程。返回结果后,处理器从内核态切换回用户态,并将控制权交还给应用程序。
- 应用程序继续执行:当应用程序收到open()系统调用函数的返回值后,它会根据返回值执行相应的操作,并继续执行程序的下一条指令。
综上所述,Linux系统调用的过程是一个从用户态切换到内核态,执行系统调用处理程序并返回结果的过程。对于open()系统调用,它的过程是通过sys_open函数在内核中完成打开文件的操作,并返回相应的文件描述符给应用程序。
系统调用的过程是一个从用户态切换到内核态,执行系统调用处理程序并返回结果的过程。对于open()系统调用,它的过程是通过sys_open函数在内核中完成打开文件的操作,并返回相应的文件描述符给应用程序。
5. 许愿
八股背得好,offer追着跑!!!