Linux学习05——进程管理与控制

概述

学习目标:

  • 理解逻辑流、并发流基本概念
  • 理解进程概念、结构与描述
  • 理解进程基本状态及状态转换关系图,了解进程PCB组织,分辨进程与程序的区别与联系
  • 掌握利用进程创建、程序加载、进程终止、进程撤销进行多进程并发编程基本方法
  • 理解多进程并发执行特征,掌握程序并发运行的基本分析方法
  • 理解信号机制与应用,掌握利用信号机制编程基本框架
  • 理解守护进程概念,了解应用编程方法
  • 进程基本概念(属性、结构、组织、基本状态与转换)
  • 进程创建与程序加载
  • 多进程并发特征
  • 进程等待、终止、回收 非本地跳转与应用
  • 信号机制及应用
  • 守护进程编程

5.1 逻辑控制流和并发流

逻辑控制流

  • 逻辑流:在多进程运行环境下,一个程序(或进程)按程序员意图,从main函数开始,一个一个语句,一条一条指令执行,执行轨迹为一系列程序计数器(PC)的值,给人一种每个程序都独占处理器的假象,而一般整个系统可能仅有一个CPU,各进程轮流执行,我们称进程为一个逻辑流
  • 实际上,每个进程的执行过程是不连续的,它们交替使用处理器
  • 处理器在不同进程间切换有两种原因:
  1. 主动放弃CPU:如进程执行耗时的I/O操作时(比如执行C语言的scanf语句)
  2. 进程被动放弃CPU:比如本次分配给进程的时间配额已经用完,或有紧迫程度更高的任务要执行,操作系统强行夺走CPU
  • CPU控制发生转移的时机:一般都在中断响应之时,因为只有这个节骨眼操作系统能介入控制,中断有外部中断、时钟中断等
  • 逻辑流示例:进程、中断(异常)处理程序、信号处理程序、线程和Java进程。

Linux学习05——进程管理与控制_第1张图片

并发概念

  •  并发:指多个活动在同一时间段内同时进行。
  1. 日常生活中并发:日常生活中普遍存在,也是操作系统最基本的特征。
  2. 计算机系统中并发:体现为多任务、多进程、多线程的并发执行
  • 并发有何用途
  1. 操作系统的多任务管理能力,使我们能够很好地驾驭对并发活动的复杂管理,开发出各种功能强大的信息管理系统、网络应用、购物平台;
  2. 充分发挥计算机硬件系统强大的处理能力,满足信息查询、网络购物等应用需求

并发流及相关概念

  • 并发流:一个逻辑流的执行在时间上与另一个流存在重叠情况,这两个流被称为并发地运行,生命周期存在重叠的2个进程是并发流,或称并发进程。
  • 并发(concurrency):多个流并发地执行的一般现象
  • 多任务(multitasking):一个进程和其他进程轮流运行的概念
  • 时间片(time slice) :每次分配给一个进程的执行时间称之为时间片(time slice),进程也因此划分为多个时间分片(time slicing)。
  • 判断方法:某种可能的执行模式在时间上存在重叠,它们就是并发流。

Linux学习05——进程管理与控制_第2张图片

并发与并行

  •  并发流:如果两个流在时间上重叠,那么它们就是并发的,即使它们是运行在同一个处理器上的。
  • 并行流:如果两个流同一时刻运行在不同的处理器核或者计算机上,那么我们称它们为并行流(parallel flow),并行流在某段时间内同时执行。
  • 并行流是并发流的一个真子集。

Linux学习05——进程管理与控制_第3张图片

5.2 进程的概念

5.2.1 进程概念、结构与描述

进程

  • 不严格的定义:进程是正在执行中的程序
  • 较为严格的定义:进程是程序在一个独立数据集上的执行过程。
  • 示例: Shell窗口、Linux 命令执行过程
  • Linux系统进程家庭: Linux允许多个用户同时登录系统。每个用户可以同时运行许多个程序,或者同时运行同一个程序的许多个实例,每个程序运行实例都是一个进程。系统本身也运行着一些管理系统资源和控制用户访问的程序

进程结构

作为程序执行过程的进程,至少需包含三项内容:程序代码、数据集和进程控制块(PCB, Process Control Block)

  • 程序代码:一般是一个包括main函数的可执行程序,程序装载到内存,进程才能启动
  • 数据集:进程的处理对象,可认为是变量内容,保存初始化信息、环境变量、命令行参数和文件的数据
  • PCB:保存程序代码、数据变量地址、进程其他属性,PCB是进程存在的唯一标志,以后操作系统就通过PCB来对进程实施管理和控制

Linux学习05——进程管理与控制_第4张图片

进程属性(保持在PCB中)

 (1)进程描述信息:        

  • 进程号(PID)        
  • 用户标识          
  • 用户组标识          
  • 进程族亲信息:父进程标识、兄弟进程标识

(2)进程控制信息          

  • 进程状态          
  • 调度信息:优先级、剩余时间片和调度策略          
  • 记时信息:CPU使用时间等          
  • 通信信息:未处理信号、管道、信号量、消息队列、共享内存等

(3)进程资源信息        

  • 存储器地址        
  • 打开文件的信息

(4)CPU现场信息        

  • 当前进程CPU寄存器副本:程序计数器PC、通用寄存器、                                              
  • 标识寄存器FLAGS

PCB实例(Linux任务结构体:task_struct)

Linux学习05——进程管理与控制_第5张图片

5.2.2 进程的基本状态及状态转换

一般根据CPU对资源拥有情况,定义三种基本状态:

  • 就绪状态(ready state)。进程已分配到除CPU以外的所有必要资源,只要获得CPU,便可立即执行,该状态为就绪状态
  • 运行状态(也称执行状态, running state):进程已包括CPU在内的所有运行所需资源,正在执行中
  • 阻塞状态(blocked state):正在执行的进程因请求资源、等待事件发生、等待I/O等原因,而暂时无法继续执行时,进入阻塞状态

实际操作系统设计中,往往还需增加2个状态:

  • 创建状态:正在创建且尚未完成创建过程的进程(处于胎儿期)所处的状态称为创建状态
  • 终止状态:进程终止后并不立即将其清理,而是让其进入终止状态。

Linux学习05——进程管理与控制_第6张图片

5.2.3 对进程PCB进行组织

Linux系统以双向链表、树形链表等多种形式进行组织:

(1)双向链表队列

  • 根据进程状态将进程PCB按双向链表组织成多个双向链表,
  • Linux用prev_task和next_task两个指针来构建进程队列
  • 可设置一个就绪队列和多个阻塞队列,每种等待事件设置一个等待队列
  • 好处:方便快速取得队列中第一个进程PCB

Linux学习05——进程管理与控制_第7张图片

 双向链表+树形结构

按族亲关系组成双向链表+树形结构:

  • 树形结构为父子关系,父节点为父进程,子节点为子进程,用p_pptr、p_cptr两个指针管理
  • 子进程间构成双向链表结构:用p_osptr和p_ysptr两个指针维护

Linux学习05——进程管理与控制_第8张图片

5.2.4 进程实例

在两个窗口运行pro1.c,然后在另一个窗口显示进程信息

Linux学习05——进程管理与控制_第9张图片

 Linux学习05——进程管理与控制_第10张图片

5.2.5 操作进程的工具

ps命令查看进程信息

(1)显示全部进程信息:ps  -ef

Linux学习05——进程管理与控制_第11张图片

(2)从ps命令中过滤出指定进程信息:ps  -ef | grep  bash

 (3) ps -u命令显示当前用户拥有进程资源消耗信息

(4)ps  l命令显示当前用户拥有进程的进程信息

各列含义:

  • UID:用户ID
  • PID:进程唯一编号
  • PPID:父进程ID
  • PRI:进程优先级
  • STAT:进程状态
  • TTY:进程启动端口
  • COMMAND:进程是通过启动哪个命令产生的 

用kill终止进程

Linux学习05——进程管理与控制_第12张图片

进程被kill后,其状态发生何种变化?

答:kill之后终端1会自动关闭 

后台执行进程(命令后加符号”&”)

好处:输入命令串 后,立即显示命令提示符,前面命令的执行和下一条命令的输入可并发执行 

Linux学习05——进程管理与控制_第13张图片

5.2.6 编程读取进程属性

应用程序常常需要读取进程标识PID、父进程标识PPID、用户标识UID、组标识GID等信息。

Linux提供了getpid、getppid系统调用函数,用于进程获取自身与父进程的PID,函数原型如下:

#include
#include
pid_t getpid(void);  //返回当前进程PID
pid_t getppid(void); //返回父进程PPID

其他系统函数,获取相关信息

#include 
#include 
uid_t  getuid(void);    //返回当前进程实际用户ID(UID),
                                    //启动进程用户ID
uid_t  geteuid(void);   //返回实际有效用户ID,EUID
                                    //进程对文件实施某种操作时,
                                      //如果用户EUID有该权限,系统就授权
gid_t  getgid(void);    //返回当前进程实际用户组ID,GID
                                    //启动进程用户组ID,GID
uid_t  getegid(void);   //返回实际有效用户组ID,EGID
                                   //进程对文件实施某种操作时,
                                   //若EUID对文件无某种操作权限
                                   //EGID有组权限,也可授权

实例:

Linux学习05——进程管理与控制_第14张图片

Linux学习05——进程管理与控制_第15张图片

5.3 进程控制

5.3.1 创建进程

  • Linux系统启动时,生成一个名为init的进程,PID为1,称为初始化进程,是其他所有进程的祖先进程,其他进程都是由init进程或其子孙进程创建的
  • 创建新进程归根结底是通过父进程执行fork系统调用函数来实现的,fork函数的作用是复制进程
  • 一般父进程先调用fork函数复制出子进程,再让子进程调用exec系统来加载不同的程序代码
  • 子进程也创建自己的子进程,最终创建一个丰富多彩的进程世界,形成一棵以init进程为祖先的进程树

创建进程

#include 
#include 
pid_t  fork(void);
//成功子进程返回0,父进程返回PID;失败返回-1

示例1:

Linux学习05——进程管理与控制_第16张图片

Linux学习05——进程管理与控制_第17张图片

fork语句复制一个与父进程一样的代码,父进程执行pid>0分支,子进程执行pid==0分支。父子进程是两个独立的进程实体,互不相关,并发执行。

多进程并发

要编写多进程并发,首先确定要创建几个进程,亲族关系如何,画出框架图,然后填写代码。

示例:

  • 框架图

Linux学习05——进程管理与控制_第18张图片

  •  代码

Linux学习05——进程管理与控制_第19张图片

Linux学习05——进程管理与控制_第20张图片

5.3.2 多进程并发特性与执行流程分析

示例

Linux学习05——进程管理与控制_第21张图片

Linux学习05——进程管理与控制_第22张图片

使用 fork语句创建的子进程是一个独立的进程实体,父子进程的多并发活动可以交错进行、同时进行或错开进行。

5.3.3 进程的终止与回收

进程终止    

1. 进程终止方式

一个进程完成其处理任务或非正常结束时,会归还分配给其程序代码与数据变量的存储器资源及所有其他资源,包括进程控制块占用的存储器。进程有正常终止和异常终止两种方式:

  • 正常终止:正常终止有几种情况 完成main函数执行 在main函数执行return返回 执行exit函数调用而结束,一般为exit(0)
  • 异常终止:也有几种方式 执行abort函数调用 按Ctrl_C键 程序执行出错:如被0除、存储器访问越界、数学运算溢出 被信号而终止:如收到kill命令发出的信号

2. 进程终止状态

  • 常见终止状态(约定成俗):

Linux学习05——进程管理与控制_第23张图片

  •  进程终止状态设置:exit和abort函数
#include 
void exit (int status);               //若进程因执行exit()正常终止,则其终止状态为status
void abort (void);                   //终止状态为06,人为规定,见表5-1  128+6=134
  • 通过终端命令获取刚结束进程终止状态(通过环境变量 $?)

进程僵尸问题

1. 进程终止后的状态

  • 进程终止后,大部分资源都已归还给系统,但仍占用进程PID,保留其进程控制块PCB,其中包含退出状态和一些其他对父进程有用的信息,等待父进程读取和处理
  • 我们称已经执行结束、但PCB仍存在的进程为僵尸进程,僵尸进程虽然有PCB但已经不可能再次运行了,因为它仅剩下一个躯壳PCB,已经没有灵魂(代码、数据)了
  • 子进程终止时,会向父进程发送一个通知信号,父进程可执行函数调用waitpid()来读取子进程退出状态和其他信息,并彻底销毁子进程,归还其PCB
  • 进程状态为Z或者的进程为僵尸进程

2. 僵尸进程示例(zombie.c):子进程已经终止但父进程尚未执行waitpid对其执行清理

Linux学习05——进程管理与控制_第24张图片

Linux学习05——进程管理与控制_第25张图片

 进程回收(清理)

1、回收函数

#include 
#include 
pid_t waitpid (pid_t pid, int *status , int options);
//返回:如果成功,则为子进程的PID,如果出错,则为-1..
pid_t wait (int *status);
//返回:如果成功,则为子进程的PID. 如果出错,则为-1. 

2. 如何从waitpid获取的status判断进程退出方式

  • WIFEXITED(status):如果子进程通过调用exit或者一个返回(return)正常终止,就返回真
  • WIFSIGNALED(status):如果子进程是因为一个信号终止的,那么就返回真

3. 如何读取进程退出状态

WEXITSTATUS(status):返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态

4、示例

Linux学习05——进程管理与控制_第26张图片

5.3.4 让进程休眠

主要函数

主要函数:
#include 
unsigned int sleep(unsigned int secs);       //睡眠若干秒
void  usleep(unsigned int usecs);              //睡眠若干微秒
int  pause(void);                                     //无限期休眠
int scanf(…);                                          //等待输入
int  printf(…);                                         //等待输出完成

5.3.5 加载并运行程序

加载函数

#include 
int execve(const char *filename, const char *argv[] , const char *envp[] ) ;
int execvp(const char *filename, const char *argv[]) ;
int execlp(const char * file,const char * arg,....);
//成功不返回,错误返回-1

参数列表argv、环境变量列表组织结构

 Linux学习05——进程管理与控制_第27张图片

main函数执行时堆栈结构

为什么形式参数argv、envp是局部变量,因为在堆栈中为其分配地址

Linux学习05——进程管理与控制_第28张图片

 操作环境变量的函数

#include  
char *getenv (const char *name);    //读取环境变量值
    //返回:若存在则为指向name的指针,若无匹配的,则为NULL.
int setenv (const char *name,  const char *newvalue,  int overwrite); 
                                                 //更改环境变量
    //返回:若成功则为0,若错误则为-1.
void unsetenv (const char *name);   //取消环境变量

示例:

Linux学习05——进程管理与控制_第29张图片 Linux学习05——进程管理与控制_第30张图片

5.3.6 fork和exec函数的应用实例

用fork、exec实现shell (shellex.c)

要求:

  • (1)外壳打印一个命令行提示符>
  • (2) 等待用户在stdin上输入命令行,然后解析和执行命令
  • (3)支持一条内置命令----quit命令,用于终止shell进程

示例:

Linux学习05——进程管理与控制_第31张图片

Linux学习05——进程管理与控制_第32张图片

Linux学习05——进程管理与控制_第33张图片 Linux学习05——进程管理与控制_第34张图片

Linux学习05——进程管理与控制_第35张图片

实现IO重定向

Linux学习05——进程管理与控制_第36张图片

Linux学习05——进程管理与控制_第37张图片

5.3.7 进程与程序的区别

  • (1)程序是永存的,作为源代码或目标模块存在于外存;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的。
  • (2)程序是静态的,关机后仍然存在,进程是动态的,有从产生到消亡的生命周期。
  • (3)进程具有并发性,而程序没有。
  • (4)进程和程序不是一一对应的:一个程序可对应多个进程即多个进程可执行同一程序;一个进程可以执行一个或几个程序。

5.4 信号机制

5.4.1 信号概念

CPU处理突发事件的机制—中断机制    

在CPU执行程序的过程中,出现了某种紧急或异常的事件(如网卡数据到达、电源掉电、运算溢出), CPU将暂停正在执行的程序,转去(执行中断服务程序处理该事件,并在处理完毕后返回断点处继续执行被暂停的程序,这一过程称为中断,CPU有专门用于处理中断的硬件单元,称为中断机制

Linux学习05——进程管理与控制_第38张图片

进程处理突发事件的机制--信号机制

 1. 信号概念和种类  

  • 信号机制是在进程层面上对CPU中断机制的一种模拟,一个信号就是一条小消息,它通知进程系统中发生了一件与该进程相关某种事件。
  • 这些事件可能来自于用户操作、内核、本进程或其它进程。
  • 每种信号用一个1~31或1~63的整数表示,未处理的信号用一个32位或64位整型变量记录,每种信号对应其中一个二进制位。
  • 在终端窗口输入“man 7 signal”就能查得该系统支持的所有信号名称及及其编号
  • 进程收到某个信号后,都需执行某种操作(或某个程序)对其进行处理,缺省处理方式一般有忽略和终止进程两种,一般收到表示致命错的信号,进程都会显示相关描述信息而终止。
  • 用户可设置信号处理函数,按定制方式进行信号处理。

2. Linux系统支持的信号种类

Linux学习05——进程管理与控制_第39张图片

5.4.2 信号术语

信号传递有两个步骤

(1)发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。发送信号的原因有:

  • 内核检测到一个系统事件,比如被零除错误或者子进程终止
  • 一个进程调用kill函数(下一节讨论)给目的进程发信号
  • 进程也可通过调用raise、alarm等函数给自己发信号

(2)接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,目的进程就接收了信号。

  • 进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号
  • 图5-23给出了信号处理程序捕获信号的基本思想

Linux学习05——进程管理与控制_第40张图片

信号记录与术语

  • 待处理信号(pending signal):一个已经发出但没有被接收的信号叫做待处理信号
  1. 由于传统上,所有待处理信号一般用一个整数变量表示,每种信号用其中一位表示
  2. 在任何时刻,一种类型至多只有一个待处理信号,如果前面有未处理的类型为k的信号,则其后类型为k的未处理信号会被丢弃,而不是进行信号排队
  • 信号阻塞:一个进程可以有选择性地阻塞接收某种信号
  • 当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞
  • 内核为每个进程在pending位向量(整数变量)中维护着待处理信号的集合,而在blocked位向量(整数变量)中维护着被阻塞的信号集合,这两个字段都定义在task_struct结构体中
  • 只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接收了一个类型为k的信号,内核就会清除pending中的第k位
  • 调用信号处理程序称为捕获信号。执行信号处理程序称为处理信号

5.4.3 发送信号的过程

Unix系统提供了多种向进程发送信号的机制。所有这些机制都是基于进程组(process group)这个概念的

进程组概念

  • 每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。
  • 进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号
  • 一个进程调用”pid_t getpgrp(void)”函数返回当前进程的进程组ID
  • 调用”int setpgid(pid_t pid,  pid_t pgid)”函数来改变自己或者其他进程的进程组
  1. 如果pid是0,那么就使用当前进程的PID
  2. 如果pgid是0,那么就用pid指定的进程的PID作为进程组ID
  3. 示例:”setpgid(0, 0);”会创建一个新的进程组,其进程组ID是15213,并且把进程15213加入到这个新的进程组中
  • 外壳为每个作业创建一个独立的进程组,一般来说,进程组ID是作业中父进程PID。
  • 进程组示例:图5-24展示了有一个前台作业和两个后台作业的外壳。前台作业中的父进程PID为20,进程组ID也为20。父进程创建两个子进程,每个也都是进程组20的成员。

Linux学习05——进程管理与控制_第41张图片

 用/bin/kill程序发送信号

  • /bin/kill程序可以向另外的进程发送任意的信号
  • 示例: “$ ki11 -9  15213”  发送信号9 (SIGKILLL)给进程15213

从键盘发送信号

  • 键入ctrl-c:发送SIGINT信号发送到这个前台进程组中的每个进程
  • 键入ctrl-z:发送一个SIGTSTP信号到外壳,外壳捕获这个信号,并发送SIGTSTP信号给前台进程组中的每个进程

用kill和raise函数发送信号

函数原型

#include
#include
int kill(pid_t pid, int sig);
int raise(int sig);
//返回值;若成功,则为0;若失败,则为-1.

进程通过调用kill函数发送信号给其他进程(包括它们自己): int kill(pid_t pid,  int sig);通过调用raise函数向自己发生信号。

用alarm函数发送信号

  • 进程可以通过调用alarm函数向它自己发送SIGALRM信号
  • alarm函数安排内核在secs秒内发送一个SIGALRM信号给调用进程,相当于给进程设置一个闹钟
  • alarm函数调用都将取消任何待处理闹钟,返回待处理的闹钟在被发送前还剩下的秒数。

5.4.4 接收信号的过程

  • 当内核从一个异常处理程序返回,准备将控制传递给进程p时,它会检查进程p的未被阻塞的待处理信号的集合(pending&-blocked)。
  • 如果这个集合为空(通常情况下),那么内核将控制传递到p的逻辑控制流中下一条指令(Inext)
  • 如果集合是非空的,那么内核选择集合中的某个信号k(通常是最小的k),并且强制p接收信号k,执行信号处理
  • 一旦进程完成了这个行为,那么控制就传递回p的逻辑控制流中下一条指令(Inext)
  • 每种信号都有一种缺省处理方式:进程终止、进程忽略该信号、进程终止并转储存储器、进程停止直到被SIGCONT信号重启
  1. 如:收到SIGKILL的默认行为就是终止接收进程
  2. 接收到SIGCHLD的默认行为就是忽略这个信号
  • 进程可以通过使用signal函数修改和信号相关联的默认行为:
     
typedef  void (*sighandler_t)(int);

sighandler_t signal(int signum,  sighandler_t  handler);
  1. 如果handler是SIG_IGN,那么忽略类型为signum的信号
  2. 如果handler是SIG_DFL,那么类型为signum的信号恢复为默认行为
  3. 否则,handler就是用户定义的函数的地址

若调用成功,则为指向前次处理程序的指针

示例:

Linux学习05——进程管理与控制_第42张图片

5.4.5 信号处理问题

对于只捕获一个信号并终止的程序来说,信号处理是简单直接的。然而,当一个程序要捕获多个信号时,会产生一些微妙的问题。

  • 待处理信号被阻塞。

Unix信号处理程序通常会阻塞相同类型待处理信号。比如,假设一个进程捕获了一个SIGINT信号后,正在执行SIGINT处理程序。如果另一个SIGINT信号传递到进程,这个SIGINT将变成待处理,而不会被接收,直到处理程序返回。 待处理信号不会排队等待。每个类型至多只有一个待处理信号。因此,如果有两个类型为k的信号传递到某个进程,而该进程正在执行信号k的处理程序,则信号k是阻塞的,后续的信号k会被简单丢弃,而不会排队等待。

  • 系统调用可以被中断。

像read、wait和accept这样的系统调用潜在地会阻塞进程一段较长的时间,称为慢速系统调用。在某些系统中,当处理程序捕获到一个信号时,被中断的慢速系统调用在信号处理程序返回时不再继续,而是立即返回给用户一个错误条件,并将errno设置为EINTR。

示例一(signal1.c):父进程利用SIGCHLD来收割子进程

Linux学习05——进程管理与控制_第43张图片Linux学习05——进程管理与控制_第44张图片

Linux学习05——进程管理与控制_第45张图片

 问题:

  • 子进程向父进程发送了三个SIGCHLD信号,但父进程仅接收两个,子进程627没有被回收,成为僵死进程
  • 原因是父进程在处理一个SIGCHLD信号时,收到第二、第三个信号,第二个信号被阻塞,第三个信号被丢弃

 改进方法一:SIGCHLD处理程序每次被调用时,尽可能回收更多的僵死子进程(signal2.c)

Linux学习05——进程管理与控制_第46张图片

Linux学习05——进程管理与控制_第47张图片

Linux学习05——进程管理与控制_第48张图片

 改进方法二:手动地重启被终止的read调用(signal3.c)

Linux学习05——进程管理与控制_第49张图片

Linux学习05——进程管理与控制_第50张图片

Linux学习05——进程管理与控制_第51张图片

5.4.6 可移植信号处理

不同系统之间,信号处理语义的差异(比如一个被中断的慢速系统调用是重启还是永久放弃)是Unix信号处理的一个缺陷。为了处理这个问题,Posix标准定义了sigaction函数,它允许像Linux和Solaris这样与Posix兼容的系统上的用户,明确地指定他们想要的信号处理语义

#include
int sigaction(int signum, struct sigaction *act, struct sigaction *oldact);

使用sigaction函数,只有这个处理程序当前正在处理的那种类型的信号被阻塞,信号不会排队等待,但允许被中断的系统调用会自动重启 sigAction使用比较复杂,要求用户设置多个结构条目, W. Richard Stevens编写了一个包装函数Signal,方便我们使用:

Linux学习05——进程管理与控制_第52张图片

回收子进程改进方法四:使用Signal函数安装信号处理函数 (signal4.c)

Linux学习05——进程管理与控制_第53张图片

Linux学习05——进程管理与控制_第54张图片

Linux学习05——进程管理与控制_第55张图片

5.5 守护进程

  • 在Linux或者UNIX操作系统中,当系统引导的时候,会开启很多服务为用户提供某种功能,这些服务就叫做守护进程或 Daemon进程,例如ftp服务、计划任务进程crond、http进程httpd
  • 守护进程是脱离于终端并且在后台运行的进程,避免进程在执行过程中产生的信息在任何终端上显示
  • 守护进程也不会被任何终端所产生的信息所打断

创建守护进程的编程步骤

  • 1.调用fork()创建新的进程作为守护,父进程调用exit()终止,让守护进程后台继续执行:    if(pid=fork()) exit(0);
  • 2.脱离控制终端、登录会话和进程组,使自己成为会话组长:   setsid();
  • 3. 关闭打开的文件描述符,归还系统资源    for (i = 0; i < NR_OPEN; i++)  close (i);
  • 4. 改变当前工作目录,不要指向特定用户目录    chdir (”/”);
  • 5. 处理文件描述符0、1、2   重定向到/dev/null

守护程序自动初始化函数

  • 许多UNIX系统提供了C库函数daemon()来自动完成守护程序初始化工作,从而简化一下繁杂的工作:     #include     int daemon(int nochdir, int noclose);
  • 若参数nochdir为非0值,就不会将工作目录更改为根目录,如果参数noclose为非0值,就不会关闭所有打开的文件描述符,通常这些参数设置为0。函数执行成功时返回0,失败返回-1,并将errno设置为错误码。

5.6 进程、内核、系统调用间的关系

Linux学习05——进程管理与控制_第56张图片

你可能感兴趣的:(Linux学习笔记,学习,linux)