读书笔记
本章讨论了Unix/Linux中的进程管理;阐述了多任务处理原则;介绍了进程概念;并以一个编程示例来说明多任务处理、上下文切换和进程处理的各种原则和方法。多任务处理系统支持动态进程创建、进程终止,以及通过休眠与唤醒实现进程同步、进程关系,以及以二叉树的形式实现进程家族树,从而允许父进程等待子进程终止;提供了一个具体示例来阐释进程管理函数在操作系统内核中是如何工作的;然后,解释了Unix/Linux中各进程的来源,包括系统启动期间的初始进程、INIT进程、守护进程、登录进程以及可供用户执行命令的sh进程;接着,对进程的执行模式进行了讲解,以及如何通过中断、异常和系统调用从用户模式转换到内核模式;再接着,描述了用于进程管理的Unix/Linux系统调用,包括fork、wait,exec和exit ;阐明了父进程与子进程之间的关系,包括进程终止和父进程等待操作之间关系的详细描述;解释了如何通过INIT进程处理孤儿进程,包括当前Linux 中的subreaper进程,并通过示例演示了subreaper进程;接着,详细介绍了如何通过exec更改进程执行映像,包括execve系统调用、命令行参数和环境变量;解释了LI/O重定向和管道的原则及方法,并通过示例展示了管道编程的方法;读者可借助本章的编程项目整合进程管理的各种概念和方法,实现用于执行命令的sh模拟器。sh模拟器的功能与标准sh完全相同。它支持简单命令、具有1/O重定向的命令和通过管道连接的多个命令的执行。
知识点总结
一、进程的组成
一个进程包含内核中的一部分地址空间和一系列数据结构。其中地址空间是内核标记的一部分内存以供进程使用,而数据结构则用来纪录每个进程的具体信息。
最主要的进程信息包括:
进程的地址空间图
进程当前的状态( sleeping、stopped、runnable 等)
进程的执行优先级
进程调用的资源信息
进程打开的文件和网络端口信息
进程的信号掩码(指明哪种信号被屏蔽)
进程的属主
- PID :进程 ID
每个进程都会从内核获取一个唯一的 ID 值。绝大多数用来操作进程的命令和系统调用,都需要用 PID 指定操作的进程对象。 - PPID :父进程 ID
在 Unix 和 Linux 系统中,一个已经存在的进程必须“克隆”它自身来创建一个新的进程。当新的进程克隆后,最初的进程便作为父进程存在。 - UID & EUID:真实用户 ID 和有效用户 ID
一个进程的 UID 是其创建者的身份标志(也是对其父进程 UID 的复制)。通常只有进程的创建者和超级用户才有操作该进程的权限。
EUID 是一个额外的 UID,用来决定在任意一个特定时间点,一个进程有权限访问的文件和资源。对绝大多数进程而言,UID 和 EUID 是相同的(特殊情况即 setuid) - Niceness
一个进程的计划优先级决定了它能获取到的 CPU 时间。内核有一个动态的算法来计算优先级,同时也会关注一个 Niceness 值,来决定程序运行的优先顺序。
二、信号
信号属于进程级别的中断请求。它们可以作为进程间通信的手段,或者由终端发送以杀死、中断、挂起某个进程。
常见信号列表:
三、Kill 命令
kill 命令常用来终止某个进程,它可以向进程传递任意信号(默认为 TERM)。
kill [-signal] pid
不带任何数字(信号)选项的 kill 命令并不能保证指定进程被杀死,因为 kill 命令默认发送 TERM 信号,而 TERM 是可以被捕获、屏蔽或忽略的。
可以使用 kill -9 pid 命令强制杀死进程(9 代表 KILL 信号,不可被捕获、屏蔽或忽略)。
kill 命令需要指定进程的 PID 号。
pgrep 命令可以通过程序名称(或其他属性如 UID)筛选进程号,pkill 命令可以直接发送指定信号给筛选结果。
如 sudo pkill -u ben
该命令将发送 TERM 信号给所有属于用户 ben 的进程。
killall 命令可以通过程序名称杀死指定进程的所有实例。如:
sudo killall apache2
四、进程状态
状态 | 含义 |
---|---|
Runnable | 该进程正在(正准备)执行 |
Sleeping | 该进程正等待某些资源 |
Zombie | 该进程正努力尝试结束 |
Stopped | 该进程已挂起(不允许执行) |
五、进程管理的系统调用
1、fork()
int pid = fork()
fork()创建子进程并返回子进程的pid。
2、进程终止
(1)正常终止:当内核中的某个进程终止时,他会将_exit(value)系统调用中的值记录为进程PROC结构体中的退出状态。并通知他的二父进程并使该进程成为僵尸进程。父进程课通过系统调用找到僵尸子进程,获得其pid和退出状态
pid=wait(int *status)
(2)异常终止:当某进程遇到异常时,他会陷入操作系统内核。内核的异常处理程序将陷阱错位类型转换为一个幻数,称为信号,将信号传递给进程,时进程终止。用户可以使用命令
kill -s signal_numeber pid
向通过pid识别的目标发送信号。
3、等待子进程终止
在任何时候,一个进程都可以使用
int pid = wait(int *status);
系统调用,等待僵尸子进程。
4、环境变量
各环境变量定义为:
关键字=字符串
重要环境变量:
SHELL=/bin/bash
TERM=xterm
USER=kcw
PATH=/usr/1oca1/bin:/usr/bin:/bin:/usr/local/games:/usr/games:./
HOME= / home /kcw
SHELL:指定将解释任何用户命令的sh。
TERM:指定运行sh时要模拟的终端类型。
USER:当前登录用户。
PATH:系统在查找命令时将检查的目录列表。
HOME:用户的主目录。在 Linux 中,所有用户主目录都在/home中。
在sh会话中,可以将环境变量设置为新的(字符串)值,如:
HOME= / home / newhome
可通过EXPORT命令传递给后代sh,如
expoert HOME
六、I/O重定向
sh进程有三个用于终端IO的文件流: stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。每个流都是指向执行映像堆区中FILE结构体的一个指针,如下文所示。
七、管道
管道时用于进程交换数据的单向进程件通信通道。管道有一个读取端和一个写入端。
1、管道命令处理
在Unix/Linux中,命令行
cmd1 | cmd2
sh将通过一个进程运行cmd1,并通过另一个进程运行cmd2,他们通过一个管道连接在一起,因此cmd1的输出变为cmd2的输入
2、命令管道
命令管道又叫FIFO
(1)在sh中,通过mknod命令创建一个命令管道:
mknod mypipe p
(2)或在c语言中发出mknod()系统调用
int r = mknod("mypipe",s_IFIFP,0);
(3)进程可像访问普通文件一样发个文命名管道。
问题和实践
1、fork系统调用实践
fork()函数被调用后会立即创建一个子进程,子进程和父进程独立运行互不干扰
返回值:在父进程中返回一个大于0的数,表示创建的子进程的id;在子进程中返回 0;-1表示创建失败。
对应代码
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 pid_t pid = fork();
7 if(pid == 0){
8 printf("I'm subProcess, my pid = %d, ppid = %d
", getpid(), getppid());
9 }else if(pid > 0){
10 printf("I'm parentProcess, my pid = %d, ppid = %d
", getpid(), getppid());
11 sleep(1); // Avoid father dying in front of son
12 }else{
13 printf("fork error!
");
14 }
15 return 0;
16 }
2、ps命令
我们可以通过 ps 命令得到一个进程列表
ps [options] [--help]
常用选项
-A,显示所有进程信息
ps -ef | grep 进程关键字
找到指定的进程信息