Linux系统调用

王建峰

相关概念

程序vs进程vs命令: Linux系统上所有的操作由进程完成,进程的运行是动态的,在此之前是一个静态的程序。用户用一个程序来启动一个进程,这个程序可以是别人写好的(最终被编译成可执行文件),比如lspwdcat,也可以是我们自己写的。
系统调用: 无论如何,程序最后运行起来都是进程,并且一个程序想要在系统上跑,要用到系统调用,这是系统给用户提供的API接口。
trace命令: Linux有个命令strace,常用来跟踪进程执行时系统调用和所接收的信号。通过manstrace查看具体描述。
Glibc: 作为一个开发者,也许平时并没有直接使用系统调用,而是Glibc库。Glibc是Linux下使用的开源的标准C库它是GNU发布的libc库。Glibc即系统调用的封装。

介绍系统调用

然后本文开始介绍这些系统调用,先上图
系统调用

进程管理

linux操作系统使用fork从父进程中创建子进程,子进程execve运行程序(二进制文件),父进程waitpid等待子进程结束。
所有进程都是父进程fork出来的,对于操作系统而言第一个鼻祖进程是哪来的呢? 系统启动的时候先创建一个所有用户进程的“祖宗进程”。

内存管理

在操作系统中,每个进程都有自己的进程内存空间。其中布局就有代码段数据段
一个进程的内存空间是很大的,32位的是4G,64位的就更大了。物理空间是有限的,所以进程的空间不能事先分配好的,一定是需要的时候再分配。
brkmmap是官员堆分配内存的系统调用,分配内存数量比较小的时候,使用brk会和原来的堆的数据连在一起。当分配的内存数量比较大的时候,使用mmap,会重新划分一块区域。

文件管理

文件系统相当于公司的资料库,用于保存一些永久性质的数据。能做到长期保存,文件之所以能做到这一点,一方面是因为介质,另一方面是因为格式
对于文件的操作,无非是创建creat打开open读read写write等等
Linux中一切皆文件就包括二进制文件文本文件stdout文件Socket文件设备文件目录文件,包括进程运行起来在/proc下生成的进程号也是文件。对于每一个文件,Linux分配了文件描述符,这是一个整数。

信号处理

信号是异步处理机制,用于紧急突发情况。每一种信号都有默认动作,当然用户也能编写信号处理函数,通过sigaction系统调用进行处理。

进程间通信

本地进程之间实现数据的互通,比较常见的处理机制有消息队列共享内存
通过msgget创建一个新的队列,msgsnd将消息发送到消息队列,而消息接收方可以使用msgrcv从队列中取消息; 我们可以通过shmget创建一个共享内存块,通过shmat将共享内存映射到自己的内存空间,然后就可以读写了。

网络通信

内核中有TCP/IP网络协议栈的实现,可以通过socket来实现跨系统的进程间通信。

查看源码

下载内核源码,找到./include/asm-x86_64/unistd.h文件,里面对于系统调用的定义

hinzer@ubuntu:linux-2.6.11$ head ./include/asm-x86_64/unistd.h
#ifndef _ASM_X86_64_UNISTD_H_
#define _ASM_X86_64_UNISTD_H_

#ifndef __SYSCALL
#define __SYSCALL(a,b) 
#endif

/*
 * This file contains the system call numbers.
 *

查看过程

进程通过系统调用从用户态到内核态,用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态。对于应用开发,上层还通过glibc库(对系统调用的一个封装库)。

从glibc提供的open函数出发,剖析如何从glibc的open调用到内核的sys_open!!!


glibc封装
# 以下相关文件
./sysdeps/unix/syscalls.list   # 列出所有glibc的函数对应的系统调用
./sysdeps/unix/make-syscalls.sh # 根据上面的配置文件,对于每一个封装好的系统调用,生成一个文件
./sysdeps/unix/syscall-template.S  # 定义了这个系统调用的调用方式
./sysdeps/hppa/sysdep.h   # 通过 `vim -t PSEUDO` 找到 PSEUDO 这个宏的定义。

经过分析: open函数的代码逻辑,得出结论: 对于任何的系统调用,会调用DO_CALL。这也是一个宏,这个宏 32 位和 64 位的定义是不一样的。

32位系统调用过程

继续分析glibc源码,发现宏DO_CALL定义处unix/sysv/linux/i386/sysdep.h
理解: 用户调用通过软中断进入内核态,在系统调用表中找到对应的内核系统调用,执行内核调用之前保存用户态寄存器,执行内核调用后返回并恢复用户态。

64位系统调用过程

DO_CALL定义在源码位置unix/sysv/linux/x86_64/sysdep.h,还是将系统调用名称转换为系统调用号,放到寄存器 rax。和32位不同的是,这里是真正进行调用,不是用中断了,而是改用 syscall 指令了,而且传递参数的寄存器也变了。
理解: 用户调用通过syscall指令进入内核态,在系统调用表中找到对应的内核系统调用,执行内核调用之前保存用户态寄存器,执行内核调用后返回并恢复用户态。


整个流程


补充

系统调用表

数据结构定义在arch/x86/entry/syscall_64.c,系统调用列表输出在arch/x86/entry/syscalls/syscall_64.tbl

#系统调用号 abi类型 函数名                      系统调用名
2       common  open                    sys_open
系统调用函数声明

声明在include/linux/syscalls.h,找到有sys_open 的声明

系统调用函数实现

内核系统调用实现和声明一致,其中fs/open.c

编译规则

接下来,在编译的过程中,需要根据 syscall_32.tbl 和 syscall_64.tbl 生成自己的 unistd_32.h 和 unistd_64.h。在文件arch/x86/entry/syscalls/Makefile


参考资料

阅读 334

技术方向 : 系统内核

3 声望
0 粉丝
0 条评论
你知道吗?

技术方向 : 系统内核

3 声望
0 粉丝
宣传栏