【操作系统】 Linux 系统调用(一)
目录
- 一、系统调用
- 1. 系统调用的工作机制
- 2. 系统调用的分类
- 3. 系统调用的实现
- 4. 常见的系统调用示例
- 5. 小结
一、系统调用
系统调用(System Call)是操作系统提供给用户程序的一组接口,用于执行需要内核权限的操作。这些操作通常涉及到硬件资源的访问或管理,例如文件I/O、进程控制、内存管理等。以下是对系统调用的详细解释,包括其工作机制、分类、实现方式及常见示例。
1. 系统调用的工作机制
-
用户模式与内核模式:
- 用户模式:用户程序运行在此模式下,受到严格的权限限制,不能直接访问硬件或执行特权指令。
- 内核模式:操作系统核心代码运行在此模式下,拥有完全的硬件访问权限和执行特权指令的能力。
-
触发系统调用:
用户程序通过特定的编程接口(如C语言中的标准库函数)发起系统调用请求。常见的触发方式包括使用特定的系统调用指令(如x86架构下的int 0x80
或syscall
指令)。 -
参数传递:
系统调用所需的参数可以通过寄存器或栈传递。不同的操作系统和体系结构可能有不同的约定。例如,在Linux x86_64架构中,前6个参数通过寄存器传递(RDI
,RSI
,RDX
,RCX
,R8
,R9
),更多的参数则通过栈传递。 -
模式切换:
当触发系统调用时,处理器会从用户模式切换到内核模式。这一步骤确保了安全性和稳定性,防止用户程序直接访问或修改关键系统资源。 -
执行系统调用处理程序:
内核接收到系统调用请求后,根据系统调用号(每个系统调用都有一个唯一的编号)找到对应的处理程序并执行。 -
返回结果:
执行完毕后,内核将结果返回给用户程序,并从内核模式切换回用户模式。
2. 系统调用的分类
系统调用可以根据其功能分为几大类:
-
进程控制:
- 创建新进程(
fork()
,vfork()
) - 加载和执行新程序(
exec()
系列函数) - 终止进程(
exit()
)
- 创建新进程(
-
文件管理:
- 文件打开、关闭(
open()
,close()
) - 文件读写(
read()
,write()
) - 文件定位(
lseek()
) - 文件信息获取(
stat()
)
- 文件打开、关闭(
-
设备管理:
- 设备打开、关闭
- 设备读写
-
内存管理:
- 动态内存分配(
malloc()
,free()
背后的系统调用) - 内存映射(
mmap()
)
- 动态内存分配(
-
网络通信:
- 创建套接字(
socket()
) - 连接到服务器(
connect()
) - 发送/接收数据(
send()
,recv()
)
- 创建套接字(
-
信号类:
signal/sigaction/kill
3. 系统调用的实现
-
系统调用表:
Linux内核维护了一个系统调用表(sys_call_table),其中包含了所有可用系统调用的地址。每个系统调用都有一个唯一的编号(系统调用号),这个编号用于在系统调用表中查找对应的处理程序。- 定义:Linux内核中存储所有系统调用函数指针的数组。
- 作用:当用户程序调用系统调用时,内核通过这个表找到对应的处理函数。
- 位置:位于内核内存空间,通过
/proc/kallsyms
可以查看其地址。/proc/kallsyms
文件内容:包含内核中所有符号的地址和名称。- 格式:地址 类型 符号名 [模块名]。
- 权限:通常需要root权限才能查看完整信息。
- 查看系统调用表:
cat /proc/kallsyms | grep sys_call_table
-
触发系统调用:
在x86架构下,常用的触发方式有两种:- 使用
int 0x80
中断指令(旧的方式) - 使用
syscall
指令(新的方式,效率更高)
- 使用
-
参数传递:
在x86_64架构中,系统调用号放在RAX
寄存器中,参数按顺序放在RDI, RSI, RDX, RCX, R8, R9寄存器中。如果参数超过6个,则通过栈传递。 -
错误处理:
如果系统调用成功,返回值通常是非负数;如果失败,则返回-1,并设置全局变量errno
来指示具体的错误原因。
4. 常见的系统调用示例
-
创建进程:
pid_t pid = fork(); // 创建子进程
-
打开文件:
int fd = open("example.txt", O_RDONLY); // 以只读模式打开文件
-
读取文件:
char buffer[1024]; ssize_t bytes_read = read(fd, buffer, sizeof(buffer)); // 从文件描述符fd读取数据
-
发送网络数据:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字 send(sockfd, "Hello, Server!", 13, 0); // 向服务器发送数据
5. 小结
系统调用是操作系统与用户程序之间的桥梁,它提供了安全且高效的方式来访问底层硬件资源和操作系统服务。理解系统调用的工作机制、分类及其实现细节,对于编写高效、可靠的软件至关重要。无论是进行基础的文件操作还是复杂的网络编程,掌握系统调用都是至关重要的技能。
by 久违