Unix/Linux进程管理
多任务处理
多任务处理指的是同时执行几个独立的任务。在单处理器(单CPU)系统中,一次只能执行一个任务。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。不同任务之间的执行切换机制称为上下文切换,将一个任务的执行环境更改为另一个任务的执行环境。如果切换速度足够快,就会给人一种同时执行所有任务的错觉。这种逻辑并行性成为“并发”。在有多个CPU或处理器也可以通过同时执行不同的任务来实现多任务处理。多任务处理是所有操作系统的基础。总体来说,它是并行编程的基础。
进程
操作系统是一个多任务处理系统。在操作系统中,任务也称为进程。在实际应用中,任务和进程这两个术语可以互换使用。在第2章中,我们把执行映像定义为包含执行代码、数据和堆栈的存储区。
进程是对映像的执行。
操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的CPU时间。在操作系统内核中,每个进程用一个独特的数据结构表示,叫作进程控制块(PCB)或任务控制块(线程控制块)(TCB)等。在本书中,我们直接称它为PROC结构体。与包含某个人所有信息的个人纪录一样,PROC结构体包含某个进程的所有信息。在实际操作系统中,PROC结构体可能包含许多字段,而且数量可能很庞大。首先,我们来定义一个非常简单的PROC结构体来表示进程。
typedef struct proc{
struct proc *next;
int *ksp;
int pid;
int ppid;
int status;
int priority;
int kstack[1024];
} PROC;
多任务处理系统
多任务处理(MT)系统,说明多任务处理、上下文切换和进程处理原则。下面的程序实现了一个模拟操作系统内核模式各项操作的多任务环境,由以下几个部分组成.
- type.h 文件 type.h 文件定义了系统常数和表示进程的简单 PROC结构体。
/*********** type.h file ************/
#define NPROC 9 // number of PROCs
#define SSIZE 1024 // gtack size = 4KB
// PROC status
#define FREE 0
#define READY 1
#define SLEEP 2
#define ZOMBIE 3
typedef struct proc{
struct proc *next; // next proc pointer
int *ksp; // saved stack pointer
int pid; // pid = 0 to NPROC-1
int ppid; // parent pid
int status; // PROC status
int priority; // scheduling priority
int kstack[SSIE] // process stack
}PROC;
- ts.s文件 ts.s是汇编代码,在32位GCC汇编代码中可实现进程上下文切换。
#------------- ts.s file 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:movl running, %ebx # ebx -> PROC
movl 4(%ebx), %esp #esp = PROC.saved_sp
popf1
popl %edi
popl %esi
popl %ebp
popl %edx
popl %ecx
popl %ebx
popl %eax
ret
# stack contents = |retpc|eax|ebx|ecx|edx|ebp|esi|edi|eflag|
# -1 -2 -3 -4 -5 -6 -7 -8 -9
- 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
");
}
- t.c文件 t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数。
进程管理的系统调用
- fork()
int pid = fork()
fork()创建子进程并返回子进程的pid,如果fork()失败则返回-1。
- 进程执行顺序
在folk()完成后,子进程与父进程和系统中所有其他进程竞争CPU运行时间。接下来运行哪个进程取决于他们的调度优先级,优先级呈动态变化。
- 进程终止
1.正常终止:当内核中的某个进程终止时,他会将_exit(value)系统调用中的值记录为进程PROC结构体中的退出状态。并通知他的二父进程并使该进程成为僵尸进程。父进程课通过系统调用找到僵尸子进程,获得其pid和退出状态
pid=wait(int *status)
它还会清空僵尸子进程PROC结构体,使该结构可被另一个进程重用。
2.异常终止:当某进程遇到异常时,他会陷入操作系统内核。内核的异常处理程序将陷阱错位类型转换为一个幻数,称为信号,将信号传递给进程,时进程终止。用户可以使用命令
kill -s signal_numeber pid向通过pid识别的目标发送信号。
- 等待子进程终止
在任何时候,一个进程都可以使用
int pid = wait(int *status);
系统调用,等待僵尸子进程。如果成功,则wait()会返回僵尸子进程的PID,而且status包含僵尸子进程的exitCode。此外,wait()还会释放僵尸子进程,以供重新使用。
- exec():更改进程执行映像
进程可以使用exec()将Umode映像更改为不同的(可执行)文件。exec()库函数有几个成员
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg,...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
- 环境变量
环境变量是为当前sh定义的变量,由子sh或进程继承。他们定义了后续程序的执行环境。各环境变量定义为:关键字=字符串
在sh会话中,用户可使用env或printenv命令查看环境变量。重要环境变量:
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