第3章 Unix/Linux进程管理
知识点归纳
一、多任务管理
书中介绍,一般来说,多任务处理指的是同时进行几项独立活动的能力。在计算机技术中,多任务处理指的是同时执行几个独立的任务。
在单处理器(单CPU)系统中,一次只能执行一个任务。
多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。
不同任务之间的执行切换机制称为上下文切换,将一个任务的执行环境更改为另一个任务的执行环境。
如果切换速度足够快,就会给人一种同时执行所有任务的错觉。这种逻辑并行性称为“并发”。
在有多个CPU 或处理器内核的多处理器系统中,可在不同CPU上实时、并行执行多项任务。此外,每个处理器也可以通过同时执行不同的任务来实现多任务处理。多任务处理是所有操作系统的基础。总体上说,它也是并行编程的基础。进程进程是对映像的执行。
多道程序系统中,程序具有:并行、制约以及动态的特征。程序概念难以便是和反映系统中的情况。
- 程序是一个静态的概念
程序是完成某个功能的指令集和。系统实际上是出于不断变化的状态中,程序不能反映这种动态性。 - 程序概念不能反映系统中的并行特性
例如:两个C语言源程序由一个编译程序完成编译,若用程序概念理解,内存中只有一个编译程序运行(两个源程序看作编译程序的输入数据),但是这样无法说明白内存中运行着两个任务。程序的概念不能表示这种并行情况,反映不了他们活动的规律和状态变化。就像不能用菜谱(程序)代替炒菜(程序执行的过程)一样(这句话我稍微修改了一下,感觉应该是这样表诉才对)。
3.通过对cpu进行时分复用来实现进程的并发运行,若有多个cpu或cpu有多个内核,则可以并行运行多个进程。
struct proc{
struct proc *next;
int *ksp;
int pid;
int ppid;
int status;
int priority;
int kstack[1024];
}
4.上下文切换:将当前寄存器保存到调用切换任务的堆栈中,并将堆栈指针保存到proc.ksp中
body | eax | ecx | edx | ebx | ebp | esi | edi | eflags |
---|---|---|---|---|---|---|---|---|
-1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 |
二、进程同步
概念:进程同步是指控制和协调进程交互以确保其正确执行所需的各项规则和机制。
最简单的进程同步工具是休眠和唤醒操作。
睡眠模式
1.基本概念
有时候,进程需要等待直到某个特定的事件发生,例如设备初始化完成、I/O 操作完成或定时器到时等。
在这种情况下,进程则必须从运行队列移出,加入到一个等待队列中,这个时候进程就进入了睡眠状态。
为实现休眠操作,我们可以在 PROC结构体中添加一个event字段,并实现ksleep(int event)函数,使进程进入休眠状态。
2.两种睡眠模式
(1)可中断的睡眠状态,其状态标志位TASK_INTERRUPTIBLE
(2)不可中断的睡眠状态,其状态标志位为TASK_UNINTERRUPTIBLE
唤醒操作
当某个等待时间发生时,另一个执行实体(可能是某个进程或中断处理程序)将会调用 kwakeup(event)。
唤醒正处于休眠状态等待该事件值的所有程序。
如果没有任何程序休眠等待该程序,kwakeup()就不工作,即不执行任何操作。
三、Unix/Linux中的进程
1.进程的产生
(1)内核启动代码强制创建P0进程,设置优先级为最低
(2)P0进程初始化系统,并挂载根文件系统
(3)创建子进程P1,将进程切换到P1
(4)P1创建守护进程
(4)系统启动完毕
2.守护进程
提供系统服务,并在后台运行,不直接与用户交互
- syslogd
- inetd
- httpd
(1)中断:外部设备发送给cpu信号,请求服务
(2)陷阱:程序运行出现错误
(3)系统调用:执行内核函数
4.更改进程执行映像
#include <unistd.h>
int execl( const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv( const char *pathname, char *const argv[] );
int execle( const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );
int execve( const char *pathname, char *const argv[], char *const envp[] );
int execlp( const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp( const char *filename, char *const argv[] );
5.最终发出系统调用
int execve(const char *filename, char *const argv[], char *const envp[]);
(1)filename为可执行文件路径名
(2)argv为向可执行文件传入的参数
(3)envp为执行时的环境变量
四、I/O重定向
1.文件流和文件描述符
每个文件流都是指向执行映像堆区中FILE结构体的一个指针。每个文件流对应Linux内核中的一个打开文件。每个打开文件都用一个文件描述符(数字)表示。
2.文件流I/O和系统调用
当进程执行库函数
scanf("%s",&item);
它会试图从stdin文件输入一个(字符串)项,指向FILE结构体。如果FILE结构体的fbuf[]。
实践
fork
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int main(){
printf("before fork 1\n");
printf("before fork 2\n");
printf("before fork 3\n");
pid_t pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid ==0 ){
printf("---child : my id = %d , myparent id = %d\n",getpid(),getppid());
}else if(pid >0){
printf("---parent : my child id = %d,my id = %d , my parent id = %d\n",pid,getpid(),getppid());
}
printf("=========EOF==========\n");
return 0;
}
nfork
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int main(){
int i;
for(i=0;i<5;i++){
if(fork()==0) break;
}
if(i==0){
printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
}else if(i==1){
printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
}
else if(i==2){
printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
}
else if(i==3){
printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
}
else if(i==4){
printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
}else if(i==5){
printf("I'm parent,my id = %d\n",getpid());
}
return 0;
}
execlp
默认在/bin/寻找可执行文件
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
int main(){
pid_t pid=fork();
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid == 0){
//child
execlp("ls","ls","-l",NULL);
perror("exec error");
exit(1);
}else if(pid > 0){
//parent
sleep(1);
printf("I'm parent : %d\n",getpid());
}
return 0;
}
wait
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include<stdio.h>
int main(){
pid_t pid;
pid = fork();
if(pid==0){
//child
printf("child id = %d\n",getpid());
sleep(10);
printf("----------child die-----------\n");
return 37;
}else if(pid > 0){
//parent
int status;
pid_t wpid = wait(&status);
if(wpid==-1){
perror("wait error");
exit(1);
}
if(WIFEXITED(status)){
//normally exit
printf("child normally exit with %d\n",WEXITSTATUS(status));
}else if(WIFSIGNALED(status)){
//kill by signal
printf("child is killed by signal %d\n",WTERMSIG(status));
}
printf("---------parent wait child %d finish--------\n",wpid);
}else{
perror("fork error");
}
return 0;
}
pipe函数
用pipe实现man -k dir | grep 2
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(){
int fd[2];
if(pipe(fd)==-1){
perror("pipe create error");
exit(1);
}
pid_t pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid == 0){
//child execute man -k
close(fd[0]); //向管道写入数据,关闭管道的读端
dup2(fd[1],STDOUT_FILENO);
execlp("man","man","-k","dir",NULL);
perror("exec man -k error");
exit(1);
}else if(pid > 0){
//parent execute grep
close(fd[1]); //从管道读出数据,关闭管道的写端
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","2",NULL);
perror("exec man -k error");
exit(1);
}
return 0;
}
管道操作演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int pd[2],n,i;
char line[256];
int main()
{
pipe(pd);
printf("pd=[%d, %d]\n",pd[0], pd[1]);
if(fork()){
printf("parent %d close pd[0]\n",getpid());
close(pd[0]);
while(i++ < 10){
printf("parent %d writes to pipe\n",getpid());
n = write(pd[1], "I AM YOUR PAPA", 16);
printf("parent %d wrote %d bytes to pipe\n",getpid(), n);
}
printf("parent %d exit\n", getpid());
}
else{
printf("child %d close pd[1]\n", getpid());
close(pd[1]);
while(1){
printf("child %d reading from pipe\n",getpid());
if ((n = read(pd[0], line, 128))){
line[n]=0;
printf("child read %d bytes from pipe: %s\n", n, line);
}
else
exit(0);
}
}
}