第11章 进程与信号
进程与信号构成了Linux操作环境的基础部分。他们控制了几乎所有由Linux与其他的类Unix计算机系统所执行的活动。理解Linux与Unix如何管理进程将会使得系统程序员,程序编写者,或是系统管理处于一个有利的位置。
在这一章,我们将会了解在Linux环境中进程中如何被处理的以及如何确定在指定的时刻计算机正在做什么。我们同时也会了解如何在我们自己的程序中启动与停止其他的进程,如何使得进程发送与接收消息,以及如何避免僵尸进程。具体而言,我们将会了解下列内容:
进程结构,类型与调度
使用不同的方法启动一个新进程
父进程,子进程以及僵尸进程
什么是信号以及如何使用信号
什么是进程
Unix规范第2版,以及之前的第1版,将进程定义为"一个地址空间,在这个地址空间中运行一个或是多个线程,以及这些线程所需要的系统资源"。我们将会在第12章来了解线程。就目前而言,我们只是将进程看作一个正在运行的程序。
一个多任务操作系统例如Linux会允许多个程序同时运行。每一个正在运行的程序实例构成一个进程。对于一个窗口系统,例如X Window系统,更是如此。与Windows类似,X提供了一个图形用户接口允许多个程序同时运行。每一个程序都可以显示一个或是多个窗口。
作为一个多用户系统,Linux允许多个用户同时访问系统。每一个用户会同时运行多个程序,或是同一个程序的多个实例。系统本身运行其他的程序来管理系统资源并且控制用户访问。
正如我们在第4章所看到的,一个程序,或进程,是由程序代码,数据,变量(占用系统内存),打开的文件(文件描述符),以及环境组成。通常,Linux系统会在进程之间共享代码与系统库,从而每次在内存中只有一份代码拷贝。
进程结构
下面我们来看一下一个进程在操作系统中是如何安排的。如果两个用户,neil与rick,同时运行grep程序在不同的文件中查找不同的字符串,所运行的进程如下图11-1所示。
如果我们运行ps命令,所得到的输出结果如下所示:
$ ps -af
UID PID PPID C STIME TTY TIME CMD
rick 101 96 0 18:24 tty2 00:00:00 grep pid_t /usr/include/sys/*.h
neil 102 92 0 18:24 tty4 00:00:00 grep XOPEN /usr/include/features.h
每一个进程都被分配了一个唯一的数字,称为进程标识或是PID。这通常是2到32768之间的一个正数。当一个进程被启动时,队列中下一个未被使用的数字就会被选中,而数据由2重新开始,从而他们可以循环。数字1通常是为特殊进程init所保留的,init进程管理其他的进程。我们会在稍后讨论init进程。在这里我们可以看到由neil与rick所启动的两个进程已经被标识为101与102。
将会由grep命令所执行的程序代码存储在一个磁盘文件中。通常,Linux进程并不会改写用于保存程序代码的内存区域,所以代码是以只读方式装入内存的。我们可以在上图中看到,因为这个区域不可以被改写,所以他可以被安全的共享。
系统库也可以被共享。所以,例如在内存中就可以只需要一份printf拷贝,尽管会有多个程序调用他。这更为复杂,但是与Windows中动态链接库的工作方式相似。
正如我们在前面的图中所看到的,另一个好处就是包含可执行程序grep的磁盘文件更小,因为他并不包含共享库代码。这对于单个程序而言并不是明显,但是却在整个操作系统上为标准C库节省的大量的空间。
当然,并不是一个程序所需要的所有内容都可以共享。例如,每一个进程所使用的不同变量。在这个例子中,我们可以看到搜索字符串是作为一个进程数据空间的变量s传递给grep命令的。这些是单独的,并且通常不能被其他的进程所读取。两个grep命令中所用的文件也是不同的,进程拥有其自己的用户文件访问的文件描述符集合。
另外,进程拥有其自己的堆栈空间,用于函数中的局部变量以及函数调用与返回的控制。同时他也拥有其自己的环境空间,包含为进程使用所建立的环境变量,正如我们在第4章了解putenv与getenv时所看到的。一个进程必须同时维护其自己的程序计数,在其执行中执行到哪里,哪一个是执行线程等的记录。在下一章,我们将会看到当我们使用线程时,进程可以有多个执行线程。
在许多Linux系统以及一些Unix系统上,在/proc目录中有一个特殊的文件集合。特殊之处就在于他们并不是真正的文件,而是允许我们在进程运行时查看进程的内部,就如同他们是目录中的文件一样。我们已经在第3章简要的了解了/proc文件系统。
最后,与Unix类似,因为Linux有一个虚拟内存系统可以将代码与数据换出到一个磁盘区域,可以管理更多的进程,而不仅是只适合物理内存。
进程表
Linux进程表是一个描述当装载入的所有进程的数据结构,例如,他们的PID,状态以及命令字符串,由ps所输出的信息等。操作系统使用他们的PID来管理进程,而他们被用作进程表中的索引。进程表是大小限制的,所以一个系统支持的进程数目也是有限制的。早期的Unix系统有支持256进程的限制。更为现代的实现已经放宽了这个限制,而所支持的进程数目只受形成一个进程表项的可用内存的限制。
查看进程
ps命令可以显示我们正在运行的进程,另一个用户正在运行的进程,或是系统上的所有进程。如下面的例子输出:
$ ps -af
UID PID PPID C STIME TTY TIME CMD
root 433 425 0 18:12 tty1 00:00:00 [bash]
rick 445 426 0 18:12 tty2 00:00:00 -bash
rick 456 427 0 18:12 tty3 00:00:00 [bash]
root 467 433 0 18:12 tty1 00:00:00 sh /usr/X11R6/bin/startx
root 474 467 0 18:12 tty1 00:00:00 xinit /etc/X11/xinit/xinitrc --
root 478 474 0 18:12 tty1 00:00:00 /usr/bin/gnome-session
root 487 1 0 18:12 tty1 00:00:00 gnome-smproxy --sm-client-id def
root 493 1 0 18:12 tty1 00:00:01 [enlightenment]
root 506 1 0 18:12 tty1 00:00:03 panel --sm-client-id default8
root 508 1 0 18:12 tty1 00:00:00 xscreensaver -no-splash -timeout
root 510 1 0 18:12 tty1 00:00:01 gmc --sm-client-id default10
root 512 1 0 18:12 tty1 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 tty2 00:00:00 bash
neil 655 428 0 18:24 tty4 00:00:00 -bash
root 713 1 2 18:27 tty1 00:00:00 gnome-terminal
root 715 713 0 18:28 tty1 00:00:00 gnome-pty-helper
root 717 716 13 18:28 pts/0 00:00:01 emacs
root 718 653 0 18:28 tty2 00:00:00 ps –af
这个输出显示了许多进程的信息,包括Linux系统上运行在X下的Emacs编辑器所调用的进程。例如,TTY列显示了由哪一个终端启动,TIME列表给出到目前为止所用的CPU时间,而CMD列表显示启动这个进程所用的命令。下面我们来仔细的看一下其中的一些。
neil 655 428 0 18:24 tty4 00:00:00 –bash
初始登陆是在4号虚拟控制台上执行的。这只是这个机器的一个控制台。所运行的shell程序是Linux默认的bash。
root 467 433 0 18:12 tty1 00:00:00 sh /usr/X11R6/bin/startx
X Window系统是由命令startx来启动的。这是一个启动X服务器的shell脚本并且运行一个初始化的X程序。
root 717 716 13 18:28 pts/0 00:00:01 emacs
这个进程代表在一个X窗口中运行的Emacs。他是由窗口管理器为了响应一个新窗口请求而启动的。一个伪终端,pts/0,赋给这个shell用于读取与写入。
root 512 1 0 18:12 tty1 00:00:01 gnome-help-browser --sm-client-i
这是由窗口管理器所启动的GNOME帮助浏览器。
默认情况下,ps程序只显示那些与一个终端,一个控制台,一个串行线,或是一个伪终端相连的进程。其他进程的运行并不需要与一个终端上的用户相关联。这些通常是Linux用来管理共享资源的系统进程。我们可以使用ps命令的-a选项来查看所有这样的进程,并且使用-f选项可以查看全部信息。
系统进程
下面是Linux系统上所运行的其他一些进程。输出已经进行简化处理。
$ ps -ax
PID TTY STAT TIME COMMAND
1 ? S 0:05 init
2 ? SW 0:00 [keventd]
3 ? SW 0:00 [kapmd]
4 ? SWN 0:00 [ksoftirqd_CPU0]
5 ? SW 0:00 [kswapd]
6 ? SW 0:00 [bdflush]
7 ? SW 0:00 [kupdated]
8 ? SW 0:00 [kinoded]
10 ? SW 0:00 [mdrecoveryd]
75 ? SW< 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 eth0
744 ? S 0:00 /usr/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:00 /usr/sbin/cron
1071 ? S 0:00 /usr/sbin/nscd
1084 ? S 0:00 /usr/sbin/nscd
1094 tty1 S 0:00 /sbin/mingetty --noclear tty1
1095 tty2 S 0:00 /sbin/mingetty tty2
1096 tty3 S 0:00 /sbin/mingetty tty3
1097 tty4 S 0:00 /sbin/mingetty tty4
1098 tty5 S 0:00 /sbin/mingetty tty5
1099 tty6 S 0:00 /sbin/mingetty tty6
1102 ? S 0:00 /usr/X11R6/bin/xdm
1106 ? S 0:02 /usr/X11R6/bin/X :0 vt07 -auth /var/lib/xdm/authdir/a
1108 ? S 0:00 -:0
1124 ? S 0:00 /usr/X11R6/bin/xconsole -notify -nostdin -verbose -ex
1155 ? S 0:00 -192.168.0.25:0
1168 ? S 0:00 /bin/sh /usr/X11R6/bin/kde
1259 pts/2 S 0:00 /bin/bash
1262 pts/1 S 0:00 /bin/bash
1273 pts/2 S 0:00 su -
1274 pts/2 S 0:00 -bash
1313 pts/1 S 0:00 emacs
1321 ? S 0:02 kdeinit: khelpcenter
1329 ? S 0:02 kdeinit: konqueror --silent
1357 pts/2 R 0:00 ps -ax
在这里我们可以看到一个非常重要的进程。
1 ? S 0:05 init
通常而言,每一个进程都是由另一个被称之为其父进程的进程来启动的。被启动的进程被称之为子进程。当Linux启动时,他运行init程序,他是最重要的祖先进程而其进程号为1。如果我们喜欢,可以将其称之为操作系统进程管理器,并且是所有进程的父进程。我们稍后将要看到的其他系统进程都是由init进程或是由init进程所启动的其他进程来启动的。
这样的一个例子就是登陆过程。init为我们用来登陆的每一个串行终端或是调制解调器中的拨号启动一个getty程序。如下所示的ps的输出:
1095 tty2 S 0:00 /sbin/mingetty tty2
getty进程会在终端等待激活,向用户提示熟悉的登陆提示,然后将控制权传递给登陆程序,后者会设置用户环境,并且最后启动一个shell。当用户shell退出时,init就会启动另一个getty进程。
我们可以看到启动新进程并且等待他们完成的能力是系统的基础。我们在本章的后面将会看到如何在我们自己的程序中使用fork,exec以及wait系统调用来执行同样的任务。
分享到:
相关推荐
进程和信号 进程和信号进程和信号进程和信号进程和信号进程和信号进程和信号进程和信号进程和信号进程和信号
fork()调用会创建一个与父进程几乎完全相同的副本,即子进程。在成功创建子进程后,fork()返回一个整数值,若在父进程中返回的是子进程的PID,在子进程中返回0。实验中,父进程和两个子进程分别输出特定的信息,以此...
设计内容:信号通信与进程控制 主要包括如下几项: (l)进程的创建:编写一段程序,使用系统调用fork()创建两个或多个子进程。当此程序运行时,在系统中有一个父进程和其余为子进程活动。 (2)进程的控制:在程序中...
Linux中的进程信号是一种异步通信机制,用于通知进程发生了特定事件。信号的处理过程涉及到多个步骤,主要包括信号的安装、产生、注册、注销以及执行。接下来我们将深入理解这些概念。 首先,信号的安装是通过`...
信号量机制是实现进程同步的一种有效工具,由荷兰计算机科学家Dijkstra提出。信号量是一种特殊的变量,可以被多个进程共享,并通过P操作(wait)和V操作(signal)来控制对资源的访问。当一个进程访问某个资源时,会...
- **描述**:当一个终端挂断时发送给所有与该终端关联的进程。 - **默认动作**:进程终止。 - **用途**:常用于重新加载配置文件或使后台进程退出。 #### SIGINT (中断信号) - **描述**:通常由用户通过键盘输入`...
【信号通信与进程控制】是操作系统中用于进程间交互的重要机制。在这一主题下,有以下几个关键知识点: 1. **进程的创建**:通过`fork()`系统调用,可以创建新的进程。当程序运行时,它会产生一个父进程和一个或多...
在 Linux 中,信号是一种异步通信机制,允许一个进程向另一个进程发送信号,从而通知其执行某些操作。信号可以分为两类:内核信号和用户信号。内核信号是由操作系统内核生成的,而用户信号是由用户进程生成的。 在...
本示例代码着重于使用共享内存和信号量来解决进程间的通信和同步问题,这是一种高效且灵活的方法,特别是在多处理器和多线程环境中。下面我们将详细探讨这些概念以及它们在Linux系统中的实现。 **共享内存** 是一种...
信号灯是一种用于进程间通信(IPC)的机制,主要用于协调多个进程对共享资源的访问控制。它可以被视为一种内存中的标志,用来指示资源的可用性。进程可以通过读取这个标志来决定是否能够访问共享资源,并且也可以修改...
在操作系统中,进程同步与互斥是多任务环境下确保数据一致性、避免竞态条件的关键机制。System V 信号量是一种广泛使用的同步原语,尤其在Linux系统中被广泛应用。本篇将深入探讨System V 信号量的工作原理以及如何...
Linux信号是进程间通信(IPC)的一种机制,它允许一个进程向另一个进程发送异步通知。Linux信号在进程控制中的应用非常广泛,既可以通过信号传递信息,又能在一定程度上控制进程的操作。本文将详细探讨Linux信号在...
【信号量】信号量是一种特殊的变量,它可以在进程之间共享,并通过原子操作(不会被其他进程打断的操作)来改变其值。在Linux中,信号量可以分为两种类型:整型信号量(互斥锁)和记录型信号量(可以有多个等待进程...
1[实验题目] 进程的软中断通信 2[实验目的] (1)理解掌握软中断的概念和技术;... (2)若子进程向父进程发送信号,父进程接到信号后可以缺省操作、或忽视信号、或执行一函数,各是什么含义?
3. **互斥锁的概念与实现**:在多进程环境中,互斥锁是一种通过信号量实现的机制,确保同一时间只有一个进程访问特定资源。当一个进程获得锁后,其他试图获取该锁的进程将被阻塞,直到锁被释放。 4. **PV操作**:...
信号量是一种同步机制,它是一个整型变量,可以由系统自动维护,也可以由进程修改。在Linux中,信号量分为两种类型:互斥量(mutex)和信号量集。互斥量是二进制信号量,用于保护临界区,确保同一时间只有一个进程...
进程间通信之信号 sinal ) 唯一的异步通信方式 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 sinal 五 消息队列 message queue ) 六 信号量 ...
利用信号进行进程间通信:实现一个SIGINT信号的处理程序,注册该信号处理程序,创建一个子进程,父子进程都进入等待。
其中,信号(Signal)是一种轻量级、异步的通信方式,它允许一个进程向另一个进程发送一个通知,表明某个事件已经发生或者请求进程执行特定的操作。本篇文章将深入探讨Linux信号机制,包括其基本概念、主要函数如`...
**信号(Signal)**是另一种进程间通信机制,用于通知接收进程发生了某种事件。信号可以中断进程的正常执行流程,处理特定的中断情况。例如,当进程接收到`SIGINT`(中断信号)时,通常会终止运行。在Linux中,可以...