第三章学习笔记
概要
本章讨论了 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 值,来决定程序运行的优先顺序。
二、 进程的概念
操作系统是一个多任务处理系统。在操作系统中,任务也称为进程。在实际应用中,任务和进程这两个术语可以互换使用。在第2章中,我们把执行映像定义为包含执行代码、数据和堆栈的存储区。进程的正式定义∶进程是对映像的执行。
操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的 CPU时间。在操作系统内核中,每个进程用一个独特的数据结构表示,叫作进程控制块(PCB)或任务控制块(TCB)等。在本书中,我们直接称它为PROC 结构体。与包含某个人所有信息的个人记录一样,PROC结构体包含某个进程的所有信息。在实际操作系统中,PROC结构体可能包含许多字段,而且数量可能很庞大。
三、多任务处理系统
多任务处理系统,简称MT,由以下几个部分组成
1、type.h文件
type.h文件定义了系统常熟和表示进程的简单PROC结构体
2、ts.s文件
ts.s在32位GCC汇编代码中可实现进程上下文切换。
3、queue.c文件
queue.c文件可实现队列和链表操作函数。enqueue()函数按优先级将PROC输入队列中。在优先级队列中,具有相同优先级的进程按照FIFO的顺序排序。dequeue()函数可返回从队列或链表中删除的第一个元素。printList()函数可打印链表元素。
4、t.c文件
t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数。
四、进程管理的系统调用
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
5、管道
管道时用于进程交换数据的单向进程件通信通道。管道有一个读取端和一个写入端。
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)进程可像访问普通文件一样发个文命名管道。
五、实践与代码
fork()演示
fork函数演示
#include <stdio.h>
int main()
{
int pid;
printf ( "THIS Is %d MY PARENT=%d\n" ,getpid(),getppid() );
pid = fork();// fork sysca1l; parent returns child pid,
if (pid){ // PARENTEXECUTES THIS PART
printf ("THIS IS PROCESs %d CHILD PID=%d\n",getpid(),pid);
}
else{ // child executes this part
printf ("this is process %d parent=%d\n",getpid(),getppid());
}
}