Linux进程管理
多任务处理
在编程中,多任务处理是指同时执行多个任务或进程的能力。这种能力可以通过并发编程来实现,其中任务可以是同时执行的线程、进程或协程。
进程的概念
进程:进程是对映像的执行
在操作系统内核中,每个进程用一个独特的数据结构表示,叫作进程控制块(PCB)或任务控制块(TCB)等。
多任务处理系统
多任务处理系统简称MT
多任务处理系统的组成部分
type.h文件
/*********** type.h file ************/
#define NPROC 9 //number of PROCs
#define SSIZE 1024 //stack size 4KB
// PROC status
#define FREE 0
#define READY 1
#define SLEEP 2
#define ZOMBIE 3
typedef struct proc{
struct proc *next;
int *ksp;
int pid;
int status;
int priority;
int kstack [SSIZE];
}PROC;
ts.s文件
ts.s 在32位 GCC 汇编代码中可实现进程上下文切换。
/*********** ts.s file ************/
.globl running, scheduler, tswitch
tswitch:
SAVE: pushl %eax
pushl %ebx
pushl %ecx
pushl %edx
pushl %ebp
pushl %esi
pushl %edi
pushfl
movl running,%ebx # ebx -> PROC
movl %esp,4(%ebx) # PORC.save_sp = esp
FIND: call scheduler
RESUME: rnovl running,%ebx # ebx -> PROC
movl 4(%ebx) ,%esp #esp= PROC.saved_sp
popfl
popl %edi
popl %esi
popl %ebp
popl %edx
popl %ecx
popl %ebx
popl %eax
ret
queue.c文件
queue.c 文件可实现队列和链表操作函数。enqueue()函数按优先级将 PROC 输入队列中。在优先级队列中,具有相同优先级的进程按先进先出(FIFO)的顺序排序。dequeue()函数可返回从队列或链表中删除的第一个元素。printList()函数可打印链表元素。
/*********** queue.c file ************/
int enqueue(PROC **queue,PROC *p)
{
PROC *q = *queue;
if(q == 0 || p->priority> q->priority){
*queue = p;
p->next = q;
}
else{
while(g->next && p->priority <= q->next->priority)
q = q->next;
p->next = q->next;
q->next = p;
}
}
PROC *dequeue (PROC **queue)
{
PROC *p = *queue;
if (p)
*queue =(*queue)->next;
return p;
}
int printList(char *name,PROC *p)
{
printf("%s = ",name);
while(p){
printf("[8d %d]->",p->pid,p->priority);
p = p->next;
}
printf("NULL\n");
}
t.c文件
t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数。
进程同步
睡眠模式
当某进程需要某些当前没有的东西时,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因。为实现休眠操作,我们可在PROC结构体中添加一个event字段,并实现 ksleep(int event)函数,使进程进入休眠状态。
唤醒操作
当某个等待时间发生时,另一个执行实体(可能是某个进程或中断处理程序)将会调用 wakeup(event)。唤醒正处于休眠状态等待该事件值的所有程序。如果没有任何程序休眠等待该程序,kwakeup()就不工作,即不执行任何操作。
进程终止
正常终止:进程调用 exit(value),发出_exit(value)系统调用来执行在操作系统内核中的 kexit(value)。
异常终止:进程因某个信号而异常终止。当进程终止时,最终都会在操作系统内核中调用 kexit()。
MT系统中的进程管理
实现过程:
- 用二叉树的形式实现进程家族树。
- 实现 ksleepO()和kwakeup()进程同步函数。
- 实现kexit()和kwait()进程管理函数。
- 添加“w”命令来测试和演示等待操作。
Unix/Linux中的进程
进程来源
当操作系统启动时,操作系统内核的启动代码会强行创建一个PID=0的初始进程。然后系统执行它。在初始化系统后,P0复刻一个子进程P1,并把进程切换为以用户模式运行P1。
INIT和守护进程
P1的大部分子进程都是用来提供系统服务的。它们在后台运行,不与任何用户交互。它们被称为守护进程。
登录进程
除了守护进程,P1复刻了许多登录进程,每个终端上一个,用于用户登录。每个登录进程打开三个与自己的终端相关联的文件流(stdin, stdout, stderr).
sh进程
当用户成功登录时,LOGIN 进程会获取用户的 gid 和 uid,从而成为用户的进程。它将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序 sh。现在,用户进程执行sh,因此用户进程通常称为 sh 进程。它提示用户执行命令。一些特殊命令,如cd(更改目录)、退出、注销等,由sh 自己直接执行。其他大多数命令是各种 bin 目录(如/bin、/sbin、/usr/bin、/usr/local/bin 等)中的可执行文件。对于每个(可执行文件)命令,sh 会复刻一个子进程,并等待子进程终止。子进程将其执行映像更改为命令文件并执行命令程序。子进程在终止时会唤醒父进程 sh,父进程会收集子进程终止状态、释放子进程 PROC 结构体并提示执行另一个命令等。除简单的命令之外,sh 还支持 I/O 重定向和通过管道连接的多个命令。
sh模拟器
带有I/O重定向的单命令
- 提示用户输入命令行,形式为:
cmd arg1 arg2 arg3 ··· argn
- 处理简单命令:
cmd = "cd" :chdir(arg1) OR chdir(HOME) if no arg1;
cmd = "exit" : exit(0) to terminate; - 对于所有其他命令:
创建子进程;
等待子进程终止;
打印子进程的退出状态码;
继续执行步骤1; - 子进程:首先,假设命令行中没有管道。
1.处理I/O重定向:
cmd arg1 arg2 ... < infile // take inputs from infile
cmd arg1 arg2 ...> outfile // send outputs to outfile
cmd arg1 arg2... >> outfile // APPEND outputs to outfile
2.通过execve()执行cmd,传递参数
char *myargv[ ], char *env[ ]
至cmd文件,其中myargv[]是一个字符数组*;
myargv[0]->cmd,
myargv[1]->arg1,
End with a NULL pointer
sh可以在PATH环境变量目录中搜索可执行命令,因此sh模拟器也必须这样做。为此,模拟器程序必须将PATH变量标记到用于定位命令文件的各个目录中。
带有管道的命令
- 在验证了mysh适用于简单命令之后,将其扩展到处理管道。如果命令行有一个符号 |,把它分为头部和尾部,如:
cmd1 < infile | cmd 2 > outfile
head= "cmd < infile";tail = "cmd 2 > outfile"
然后通过以下步骤实现管道。
1.创建管道
2.复刻出一个子进程来共享管道
3.安排一个进程作为管道的写进程,另一个进程作为管道的读进程。
然后,让各进程execve()其命令(可能带有I/O重定向)。
多管道:如果命令行包含多个管道符号,则通过递归实现管道。