第21章 完整架构
(本章man命令查询不到相关信息的解决方法)
Linux系统可以分为内核和应用程序两个主要部分。
一、内核模式与系统调用
计算机启动之后,Linux的内核程序启动成为一个单一的内核进程。这个单一进程将执行内核的相关功能。内核进程有权调用所有的计算机资源。当应用程序运行时,内核会分配给该应用程序一定的计算机 资源。应用程序与硬件之间的互动,也必须经由内核进行。因此,即使是一个应用程序,它的运作也离不开内核。我们把内核程序的活动称为内核模式(Kernel Mode),而把应用程序的活动称为用户模式(User Mode)。
应用程序可以通过特定的接口来调用内核功能。用户单次的内核调 用,可以称为一次系统调用(System Call)。
在Shell中输入下面的命令就可以查看Linux下所有的系统调用。
$man 2 syscalls
在Linux最常见的开发语言C中,系统调用都制作成了有特定函数名的函数。你可以通过以下命令来查看系统调用read这一系统调用的说明:
$man 2 read
命令中的2,对应了系统调用类相关的查询。命令man定义的几个查询类可以通过下面的命令查看:
$man man
常见的系统调用如下:
- read,文件读取。
- write,文件写入。
- fork,复制当前进程。
- wait,等待某个进程完成。
- chdir,改变工作目录。
每次系统调用,用户程序就触发了内核的特定动作。通过系统调用这个接口,Linux实现了内核封装,隐藏了底层的复杂性,也提高了上层应用的可移植性。
二、库函数
C语言本身规定了C标准库,如printf函数,就属于C标准库。
我们可以用man来查询库函数的帮助文档。库函数的查询类是3:
$man 3 printf
除了C标准库,UNIX操作系统都会安装POSIX标准库。函数malloc就是POSIX标准库的库函数。目录/lib和/lib64下存放着Linux系统自带的库。用户也可以在/lib下安装额外的库。
三、Shell
四、用户程序
整个架构最上层的就是应用程序,应用程序是二进制的可执行文件。在/bin和/usr/bin中我们可以看到不少这样的可执行文件。这里的bin指binary,即二进制。
Shell的两种运行程序的方式:
- 直接输入程序名,如ls、man、wget。
- 在应用程序目录用"./可执行文件名"的方式,比如./a.out
第22章 函数调用与进程空间
应用程序的进程会获得一块独立的内存空间,即进程空间。
(本章主要介绍函数调用过程栈的变化过程以及堆栈知识,此章略)
第23章 穿越时空的信号
信号是一种向进程传递信息的方式。
一、按键信号
在Shell中,可以通过快捷键Ctrl+C来中断正在进行的进程,或者Ctrl+Z来中止进程。
之前在使用Shell时,都是在Shell中输入一个命令,然后启动一个前台进程,等待命令完成。在进程运行期间,Shell的命令行输入会阻塞。
Shell除了可以有一个前台进程,还可以有多个后台进程。后台进程不会阻塞Shell的命令行。比如在执行命令时使用"&"符号就可以后台运行程序。
$ping localhost > log &
可以用按键传递给Shell前台进程的信号有三个:
- SIGINT:Ctrl+C,中断(Interrupt)前台进程。
- SIGSTP:Ctrl+Z,暂停前台进程。
- SIGQUIT:Ctrl+\,退出前台进程。
二、kill命令
(略)
三、信号机制
信号是Linux系统的重要机制,它本质上是内核传递给进程的一个整数:
- 整数 信号
- 2 SIGINT
- 3 SIGQUIT
- 14 SIGALARM
我们可以用$kill -l
来查询对应的整数。
需要发信号的情况很多,它可以是内核自身产生的,比如出现分母为0的出发运算时,内核就会用SIGFPE信号来通知进行该运算的进程。信号也可以是其他进程产生的,比如用户用快捷键发出的中断信号,实际上是由Shell产生的。无论何种,信号都是由内核写入目标进程的“邮箱”,这样就生成了信号。
信号生成后,会一直躺在信箱里,等待住户(进程)来查看。每次进程完成系统调用、即将退出内核的时间,就是查看信号的最好时机。如果有信号则会进行信号处理。从信号生成到等待处理的时间,信号处于等待(Pending)状态。
四、信号处理
进程的信号处理有三种可能:
- 无视信号:信号被清楚,进程本身不采取特殊操作
- 默认操作:每个信号对应一定的默认操作,比如SIGCONT用于继续进程。
- 捕获信号:根据信号,执行程序中自定义操作
下面以bash脚本为例,说明信号处理。
(主要介绍trap捕获信号进行信号处理,此处略)
五、C程序中的信号
(主要介绍signal函数,略)