进程通信信号,在进程通信中
和进程信号构成了Linux操作环境的基本部分。它们控制着几乎所有由Linux和其他类似Unix的计算机系统执行的活动。了解Linux和Unix如何管理进程将使系统程序员、程序员或系统管理处于有利地位。
在这一章中,我们将学习在Linux环境中如何处理进程,以及如何确定计算机在指定时间正在做什么。同时,我们也会知道如何在自己的程序中启动和停止其他进程,如何让进程发送和接收消息,如何避免僵尸进程。具体来说,我们将学习以下内容:
流程结构、类型和时间安排
使用不同的方法开始一个新的过程。
父进程、子进程和僵尸进程
什么是信号,如何使用信号?
什么是过程?
Unix规范第2版以及之前的第1版都将进程定义为“一个或多个线程在其中运行的地址空间以及这些线程所需的系统资源”。我们将在第12章学习线程。就目前而言,我们只是将该过程视为一个正在运行的程序。
像Linux这样的多任务操作系统将允许多个程序同时运行。每个运行的程序实例构成一个进程。对于诸如X窗口系统之类的窗口系统来说尤其如此。与Windows类似,X提供了一个图形用户界面,允许多个程序同时运行。每个程序可以显示一个或多个窗口。
作为一个多用户系统,Linux允许多个用户同时访问系统。每个用户将同时运行多个程序,或者同一程序的多个实例。系统本身运行其他程序来管理系统资源和控制用户访问。
正如我们在第四章中看到的,一个程序,或者说进程,是由程序代码、数据、变量(占用系统内存)、打开的文件(文件描述符)和环境组成的。通常,Linux系统在进程间共享代码和系统库,因此每次内存中只有一份代码副本。
让我们来看看一个进程在操作系统中是如何安排的。如果两个用户neil和rick同时运行grep程序在不同的文件中查找不同的字符串,运行过程如下图11-1所示。
如果我们运行ps命令,输出结果如下:
$ ps -af
PPID时间TTY时间CMD
Rick 101 96 0 18:24 tty 2 00:00:00 grep PID _ t/usr/include/sys/*。h
Neil 102 92 0 18:24 tty 4 00:00:00 grep XOPEN/usr/include/features . h
每个进程都有一个唯一的编号,称为进程ID或PID。这通常是一个介于2和32768之间的正数。当启动一个进程时,将选择队列中的下一个未使用的数字,数据将从2重新开始,以便它们可以循环。数字1通常是为管理其他进程的特殊进程init保留的。我们将在后面讨论初始化过程。这里我们可以看到由neil和rick启动的两个进程已经被标识为101和102。
将grep命令要执行的程序代码存储在磁盘文件中。通常,Linux进程不会覆盖用于存储程序代码的内存区域,因此代码以只读方式加载到内存中。上图我们可以看到,因为这个区域不能重写,所以可以安全共享。
库也可以共享。所以,举个例子,你可以只在内存中需要printf的一个副本,虽然会有多个程序调用它。这更复杂,但它的工作原理类似于Windows中的动态链接库。
正如我们在上图中看到的,另一个优点是包含可执行程序grep的磁盘文件更小,因为它不包含共享库代码。这对于单个程序来说并不明显,但是对于整个操作系统上的标准C库来说却节省了不少空间。
当然,并不是一个程序需要的所有东西都可以共享。例如,每个过程使用不同的变量。在本例中,我们可以看到搜索字符串作为流程数据空间中的变量s传递给grep命令。这些是独立的,通常不能被其他进程读取。两个grep命令中使用的文件也是不同的,并且该进程有自己的一组文件描述符供用户文件访问。
另外,进程有自己的堆栈空间,用来控制函数中的局部变量以及函数的调用和返回。同时,它也有自己的环境空间,包括为进程使用而建立的环境变量,这一点我们在第四章学习putenv和getenv时可以看到。同时,一个进程必须维护自己的程序计数,在它的执行中执行到了哪里,哪一个是执行的线程等等。在下一章,我们将看到当我们使用线程时,一个进程可以有多个执行线程。
在许多Linux系统和一些Unix系统上,在/proc目录中有一个特殊的文件集合。特别的一点是,它们不是真正的文件,而是允许我们在进程运行时查看其内部,就像它们是目录中的文件一样。我们在第3章对/proc文件系统有一个简单的了解。
最后,它与Unix相似,因为Linux有一个虚拟内存系统,可以将代码和数据换出到磁盘区域,并且可以管理更多的进程,而不仅仅是物理内存。
Linux进程表是一个数据结构,描述所有加载的进程,比如它们的PID、状态、命令字符串、ps输出的信息等。操作系统使用它们的PID来管理进程,它们被用作进程表中的索引。进程表的大小是有限的,因此系统支持的进程数量也是有限的。早期的Unix系统在支持256个进程方面受到限制。对于更现代的实现,这种限制已经放宽,支持的进程数量仅受形成进程表条目的可用内存的限制。
ps命令可以显示我们正在运行的进程、另一个用户正在运行的进程或系统上的所有进程。如以下输出示例所示:
$ ps -af
PPID时间TTY时间CMD
root 433 425 0 18:12 tty 1 00:00:00[bash]
里克445 426 0 18:12 tty 2 00:00:00-狂欢
里克456 427 0 18:12 tty 3 00:00:00[巴什]
root 467 433 0 18:12 tty 1 00:00:00 sh/usr/x11r 6/bin/startx
root 474 467 0 18:12 tty 1 00:00:00 xinit/etc/X11/xinit/xinitrc-
root 478 474 0 18:12 tty 1 00:00:00/usr/bin/gnome-session
root 487 1 0 18:12 tty 1 00:00:00 gnome-sm proxy-sm-client-id def
root 493 1 0 18:12 tty 1 00:00:01[开悟]
root 506 1 0 18:12 tty 1 00:00:03 panel-sm-client-id默认值8
root 508 1 0 18:12 tty 1 00:00:00 x screen saver-无飞溅超时
root 510 1 0 18:12 tty 1 00:00:01 GMC-sm-client-id默认值10
root 512 1 0 18:12 tty 1 00:00:01 gnome-help-browser-sm-client-I
root 649 445 0 18:24 tty2 00:00:00 su
root 653 649 0 18:24 tty 2 00:00:00 bash
尼尔655 428 0 18:24 tty 4 00:00:00-狂欢
root 713 1 2 18:27 tty 1 00:00:00 gnome终端
root 715 713 0 18:28 tty 1 00:00:00 gnome-pty-helper
root 717 716 13 18:28 pts/0 00:00:01 emacs
root 718 653 0 18:28 tty 2 00:00:00 PSaf
这个输出显示了许多进程的信息,包括在Linux系统的X下运行的Emacs编辑器调用的进程。例如,TTY列显示哪个终端启动了它,时间列表显示了到目前为止使用的CPU时间,而CMD列表显示了用于启动该进程的命令。让我们仔细看看其中的一些。
尼尔655 428 0 18:24 tty 4 00:00:00狂欢
初始登录在虚拟控制台4上执行。这只是这台机器的一个控制台。正在运行的shell程序是Linux的默认bash。
root 467 433 0 18:12 tty 1 00:00:00 sh/usr/x11r 6/bin/startx
窗口系统由命令startx启动。这是一个shell脚本,它启动X服务器并运行一个初始化的X程序。
root 717 716 13 18:28 pts/0 00:00:01 emacs
这个过程表示在X窗口中运行的Emacs。它由窗口管理器启动,以响应新的窗口请求。一个伪终端pts/0被分配给这个shell进行读写。
root 512 1 0 18:12 tty 1 00:00:01 gnome-help-browser-sm-client-I
这是由窗口管理器启动的GNOME帮助浏览器。
默认情况下,ps程序只显示那些连接到终端、控制台、串行线或伪终端的进程。其他进程的运行不需要与终端上的用户相关联。这些通常是Linux用来管理共享资源的系统进程。我们可以使用ps命令的-a选项来查看所有这样的进程,使用-f选项来查看所有信息。
下面是一些运行在Linux系统上的其他进程。输出已被简化。
$ ps -ax
PID TTY统计时间命令
1 ?S 0:05初始化
2 ?SW 0:00 [keventd]
3 ?西南0:00
4 ?SWN 0:00 [ksoftirqd_CPU0]
5 ?SW 0:00 [kswapd]
6 ?SW 0:00
7 ?SW 0:00[已更新]
8 ?西南0:00[基诺代德]
10 ?软件0:00 [mdrecoveryd]
75 ?开关0:00 [lvm-mpd]
503 ?s 0:00/sbin/syslogd-a/var/lib/DHCP/dev/log
506 ?S 0:00 /sbin/klogd -c 1 -2
542 ?SW 0:00 [khubd]
614 ?S 0:00 /sbin/portmap
653 ?S 0:00 /usr/sbin/sshd
730 ?s 0:00/sbin/dhcpcd-H-D-N-Y-t 999999-H beast eth 0
744 ?s 0:00/美国/sbin/cupsd
1004 ?S 0:00 /usr/lib/postfix/master
1021 ?S 0:00 pickup -l -t fifo -u
1022 ?S 0:00 qmgr -l -t fifo -u
1037 ?S 0:00 /usr/sbin/atd
1055 ?S 0点/usr/sbin/cron
1071 ?S 0:00 /usr/sbin/nscd
1084 ?S 0:00 /usr/sbin/nscd
1094 tty 1S 0:00/sbin/mingetty-no clear tty 1
1095 tty 2S 0:00/sbin/mingetty tty 2
1096 tty 3S 0:00/sbin/mingetty tty 3
1097 tty 4S 0:00/sbin/mingetty tty 4
1098 tty 5S 0:00/sbin/mingetty tty 5
1099 tty 6S 0:00/sbin/mingetty tty 6
1102 ?S 0:00 /usr/X11R6/bin/xdm
1106 ?s 0:02/usr/x11r 6/bin/X:0 vt07-auth/var/lib/xdm/authdir/a
1108 ?S 0:00 -:0
1124 ?s 0:00/usr/x11r 6/bin/xconsole-notify-no sdin-verbose-ex
1155 ?0:00 -192.168.0.25:0
1168 ?S 0:00 /bin/sh /usr/X11R6/bin/kde
1259分/秒0:00/箱/巴什
1262分/秒0:00/箱/巴什
1273分/秒0:00苏-
1274分/秒0:00 -bash
1313分/秒0:00 emacs
1321 ?S 0:02 kdeinit: khelpcenter
1329 ?S 0:02 kdeinit: konqueror - silent
1357分/2秒0点ps -ax
这里我们可以看到一个非常重要的过程。
1 ?S 0:05初始化
一般来说,每个进程都是由称为其父进程的另一个进程启动的。启动的进程称为子进程。当Linux启动时,它运行init程序。它是最重要的祖先进程,其进程号为1。如果我们愿意,我们可以称之为操作系统进程管理器,它是所有进程的父进程。后面我们会看到的其他系统进程都是由init进程启动的,或者由init进程启动的其他进程。
一个这样的例子是着陆过程。Init为我们用来登录的每个串行终端或调制解调器拨号启动一个getty程序。显示了以下ps输出:
1095 tty 2S 0:00/sbin/mingetty tty 2
getty进程将在终端等待激活,用熟悉的登录提示提示用户,然后将控制权交给登录程序,登录程序将设置用户环境,最后启动一个shell。当用户shell退出时,init启动另一个getty进程。
我们可以看到,启动新进程并等待它们完成的能力是系统的基础。在本章的后面,我们将看到如何在我们自己的程序中使用fork、exec和wait系统调用来执行相同的任务。