信号
人们往往将信号称为**“软件中断”**。进程收到信号,就意味着某一事件或异常情况的发生。
信号的类型很多,每一种分别标识不同的事件或情况。采用不同的整数来标识各种信号类型,并以SIGxxxx 形式的符号名加以定义。
内核、其他进程(只要具有相应的权限)或进程自身均可向进程发送信号。例如,发生
下列情况之一时,内核可向进程发送信号。
- 用户键入中断字符(通常为Control-C)。
- 进程的子进程之一已经终止。
- 由进程设定的定时器(告警时钟)已经到期。
- 进程尝试访问无效的内存地址。
在shell 中,可使用kill
命令向进程发送信号。在程序内部,系统调用kill()可提供相同的功能。
收到信号时,进程会根据信号采取如下动作之一。
- 忽略信号。
- 被信号“杀死”。
- 先挂起,之后再被专用信号唤醒。
程序可选择不采取默认的信号动作,而是忽略信号(当信号的默认处理行为并非忽略此信号时,会派上用场)
或者建立自己的信号处理器。信号处理器是由程序员定义的函数,会在进程收到信号时自动调用,根据信号的产生条件执行相应动作。
信号从产生直至送达进程期间,一直处于挂起状态。通常,系统会在接收进程下次获得调度时,将处于挂起状态的信号同时送达。如果接收进程正在运行,则会立即将信号送达。
然而,程序可以将信号纳入所谓**“信号屏蔽”**以求阻塞该信号。如果产生的信号处于“信号屏蔽”之列,那么此信号将一直保持挂起状态,直至解除对该信号的阻塞。(亦即从信号屏蔽中移除。)
线程
每个进程都可执行多个线程。可将线程想象为共享同一虚拟内存及一干其他属性的进程。每个线程都会执行相同的程序代码,共享同一数据区域和堆。可是,每个线程都拥有属于自己的栈,用来装载本地变量和函数调用链接信息。
线程之间可通过共享的全局变量进行通信。借助于线程 API 所提供的条件变量和互斥机制,进程所属的线程之间得以相互通信并同步行为—尤其是在对共享变量的使用方面。此外,利用IPC 和同步机制,线程间也能彼此通信。
线程的主要优点在于**协同线程之间的数据共享(通过全局变量)**更为容易,
而且就某些算法而论,以多线程来实现比之以多进程实现要更加自然。
再者,多线程应用能从多处理器硬件的并行处理中获益匪浅。
进程组和shell任务控制
shell 执行的每个程序都会在一个新进程内发起。
比如shell篡改见了3个进程来执行以下管道命令(在当前的工作目录下,根据文件大小对文件进行排序并展示):
ls -l | sort -k5n | less
几乎所有的主流shell 都提供了一种交互式特性,名为任务控制。该特性允许用户同时执行并操纵多条命令或管道。
在支持任务控制的shell 中,会将管道内的所有进程置于一个新进程组或任务中。(如果情况很简单,shell 命令行只包含一条命令,那么就会创建一个只包含单个进程的新进程组。)
进程组中的每个进程都具有相同的进程组标识符(以整数形式),其实就是进程组中某个进程(也称为进程组组长process group leader)的进程ID。
内核可对进程组中的所有成员执行各种动作,尤其是信号的传递。支持任务控制的shell 会利用这一特性,以挂起或恢复执行管道中的所有进程。
会话、控制终端和控制进程
会话指的是一组进程组(任务)。会话中的所有进程都具有相同的会话标识符。
**会话首进程(session leader)**是指创建会话的进程,其进程ID 会成为会话ID。
使用会话最多的是支持任务控制的shell,由shell 创建的所有进程组与shell 自身隶属于同一会话,shell 是此会话的会话首进程。
通常,会话都会与某个控制终端相关。控制终端建立于会话首进程初次打开终端设备之时。对于由交互式shell 所创建的会话,这恰恰是用户的登录终端。一个终端至多只能成为一个会话的控制终端。
打开控制终端会致使会话首进程成为终端的控制进程。一旦断开了与终端的连接(比如,关闭了终端窗口),控制进程将会收到SIGHUP 信号。
在任一时点,会话中总有一个前台进程组(前台任务),可以从终端中读取输入,向终端发送输出。如果用户在控制终端中输入了“中断”(通常是Control-C)或“挂起”字符(通常是Control-Z),那么终端驱动程序会发送信号以终止或挂起(亦即停止)前台进程组。一个会话可以拥有任意数量的后台进程组(后台任务),由以“&”字符结尾的行命令来创建。
支持任务控制的shell 提供如下命令:列出所有任务,向任务发送信号,以及在前后台任务之间来回切换。
伪终端
伪终端是一对相互连接的虚拟设备,也称为主从设备。在这对设备之间,设有一条IPC(进程间通信)信道,可供数据进行双向传递。
从设备(slave device)所提供的接口,其行为方式与终端相类似,基于这一特点,可以将某个为终端编写的程序与从设备连接起来,然后,再利用连接到主设备的另一程序来驱动这一“面向终端”的程序,这是伪终端的一个关键用途。
由“驱动程序”所产生的输出,在经由终端驱动程序的常规输入处理(例如,默认情况下,会把回车符映射为换行符)后,会作为输入传递给与从设备相连的面向终端的程序。而由面向终端的程序向从设备写入的任何数据又作为“驱动程序”的输入来传递(在执行完所有常规的终端输入处理后)。换句话说,“驱动程序”所履行的功能,在效果上等同于用户通常在传统终端上所执行的操作。
伪终端广泛应用于各种应用领域,最知名的要数telnet 和ssh 之类提供网络登录服务的应用,以及X Window 系统所提供的终端窗口实现。
日期和时间
进程涉及两种类型的时间:
- 真实时间:指的是在进程的生命期内(所经历的时间或时钟时间),以某个标准时间点(日历时间)或固定时间点(通常是进程的启动时间)为起点测量得出的时间。在UNIX 系统上,日历时间是以国际协调时间(简称UTC)1970 年1 月1 日凌晨为起始点,按秒测量得出的时间,再进行时区调整。这一日期与UNIX 系统的生日很接近,也被称为纪元(Epoch)。
- 进程时间:也称CPU 时间,指的是进程自启动起来,所占用的CPU 时间总量。可进一步将CPU 时间划分为系统CPU 时间和用户CPU 时间。前者是指在内核模式中,执行代码所花费的时间(比如,执行系统调用,或代表进程执行其他的内核服务)。后者是指在用户模式中,执行代码所花费的时间(比如,执行常规的程序代码)。
time
命令会显示出真实时间、系统CPU 时间,以及为执行管道中的多个进程而花费的用户CPU 时间。
客户端/服务器架构
客户端/服务器应用由两个组件进程组成。
- 客户端:向服务器发送请求消息,请求服务器执行某些服务。
- 服务器:分析客户端的请求,执行相应的动作,然后,向客户端回发响应消息。
客户端应用通常与用户打交道,而服务器应用则提供对某些共享资源的访问。一般说来,都是众多客户端进程与为数不多的一个或几个服务器端进程进行通信。
客户端和服务器使用IPC 机制来实现彼此通信。
将某项服务封装于单独的服务器应用中,原因举例如下。
- 效率:较之于在本地的每台计算上提供相同资源,在服务器应用管理之下提供资源的一份实例,则要节约许多。
- 控制、协调和安全:由于资源(尤其是信息资源)的统一存放,服务器既可以协调对资源的访问(例如,两个客户端不能同时更新同一信息),还可以保护资源安全,令其只对特定客户端开放。
- 在异构环境中运行:在网络中,客户端和服务器应用所运行的硬件平台和操作系统可以不同。
实时性
要提供实时响应,特别是在短时间内加以响应,就需要底层操作系统的支持。由于实时响应的需求与多用户分时操作系统的需求存在冲突,大多数操作系统“天生”并不提供这样的支持。
/proc文件系统
/proc 文件系统是一种虚拟文件系统,以文件系统目录和文件形式,提供一个指向内核数据结构的接口。方便查看和改变各种系统属性。
还能通过一组以/proc/PID
形式命名的目录(PID 即进程ID)查看系统中运行各进程的相关信息。
通常,/proc 目录下的文件内容都采取人类可读的文本形式,shell 脚本也能对其进行解析。程序可以打开、读取和写入/proc 目录下的既定文件。
大多数情况下,只有特权级进程才能修改/proc 目录下的文件内容。