ch3进程学习笔记

知识点归纳

ch3进程学习笔记

进程是对映像的执行

系统资源包括内存空间、I/O设备、CPU

PCB进程控制块,PROC结构体
简易的PROC:

typedef struct proc{
    struct proc *next; // next proc pointer
    int *ksp; // saved sp: at byte offset 4
    int pid; // process ID
    int ppid; // parent process pid
    int status; // PROC status=FREE|READY, etc.
    int priority; // scheduling priority
    int kstack[1024]; // process execution stack
}PROC;
  • ksp 保存堆栈指针,以便进程恢复。当进程放弃CPU时,会将上下文保存在堆栈中。
  • pdi 进程id编号
  • ppid 父进程id编号
  • status 进程当前状态
  • priority 进程调度优先级
  • kstack[1024]进程执行时的堆栈

睡眠模式,应该就是阻塞
进程被阻塞之后,放弃CPU,转交给进程调度,自己等待资源满足后被唤醒

唤醒操作
被唤醒的进程只是进入就绪状态,进入就绪队列,是否运行还要看进程调度

fork函数(最有收获)

返回值:子进程中返回0 ,父进程中返回子进程id,错误返回-1

ch3进程学习笔记

fork()之前的代码,子进程无法执行。
fork()之后的代码,子进程有,并且可以执行。
既然如此,那子进程和父进程在fork()之后的行为,岂不是一模一样?
没错,相当于一份代码,控制两个不同进程,进行不同的行为,所以需要用到分支语句,再加上fork()函数在两个进程中不同的返回值,让父子进程进入相同代码的不同分支中执行。

那如果要创建多个子进程呢?
首先,比如要创建5个子进程,直接写一个五次的循环是不行的,因为子进程也会执行父进程fork后面代码,5次循环将会有2^5=32个进程
所以我们在子进程中加break,让子进程不再创建孙子进程
同时,由于break后,i不再增加,所以一个i值对应一个子进程,甚是巧妙!
这样,后面就可以使用i值作为分支条件,分别控制5个进程。
(具体实现见最后的实践内容)

而且父子进程共用一个文件描述符表,所以子进程的标准输入、输出、错误,都默认和父进程一样。

如果父进程先于子进程终止,孤儿进程会被孤儿院回收(用户/内核init进程)

wait和waitpid

wait:回收子进程资源,阻塞回收任意一个。

pid_t wait(int *status)
参数 status(传出)回收进程的状态。
返回值。成功。回收进程的pid
失败。-1,errno

阻塞等待子进程退出、清理子进程残留在内核的 pcb资源,通过传出参数,得到子进程结束状态

获取子进程正常终止值:
WIFEXITED(status)--》为真--》调用EXITSTATUS(status)--》得到子进程退出值。

获取导致子进程异常终止信号。
WIFSIGNALED(statusm) --》为真--》调用WTERMSIG(status) --》得到导致子进程异常终止的信号编号。

waitpid函数。指定某一个进程进行回收。可以设置非阻塞。

pid_t waitpid(pid_t pid ,int *status,int options)

参数
pid指定回收的子进程
pid>0 : 待回收的子进程id
pid=-1: 任意子进程
pid=00:同组的子进程。

options:WNOHANG指定回收方式为非阻塞。

exec函数族

该函数成功时没有返回值,调用成功后,进程就去执行另外一个程序

所以,写在exec函数后面的代码,只有在exec出错时,才有可能执行

原来进程的fork出来的代码段、数据段、堆栈等,都将被替换成新的程序的内容。但pid不变

环境变量是为当前sh定义的变量,他们定义了程序的执行环境。
使用 env 查看环境变量,export修改环境变量

ch3进程学习笔记

管道和命名管道

管道是用于进程交换数据的单向进程间通信通道,管道有一个读取端和一个写入端。

命名管道是不相关进程间的FIFO通信通道。

读取和写入管道通常是同步、阻塞操作。

阻塞:
读数据时:管道里没有数据,但是还有写入进程
写数据时:管道里没有存储空间,但还有读出进程

错误:
读数据时:管道里没有数据,而且没有写入进程
写数据时:管道没有读出进程

命名管道是伪文件,本身不占用任何空间。是文件系统的一部分,并按目录进行组织。

使用库函数mkfifo()或者系统调用mknod(X,S_IFIFO,X)创建命名管道

问题与解决思路

如何gdb调试带fork的程序?

gdb默认进入fork的父进程

可以使用命令 set follow-fork-mode child 进入使其进入子进程

如果使用默认设置,gdb会先将fork出的子进程执行完毕,再接着执行父进程。

云班课答疑区

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main(){
  6     int i;
  7     for(i=0;i<2;i++){
  8         fork();
  9         printf("-");
 10     }
 11     return 0;
 12 }

这个程序的输出结果是8个'-',即 --------,刚开始确实令人摸不到头脑。

第一次fork后,有2个进程,分别打印1个'-',共2个
第二次fork后,有4个进程,分别打印1个'-',共4个
一共应该只打印6个才对。

后来将代码改成printf("-\n"),就正常打印了6个

于是怀疑跟stdout的行缓冲有关。fork出来的子进程把缓冲区也复制了。

第一次fork后,父进程缓冲区里有1个,大儿子缓冲区里有1个
第二次fork后,printf之前,父进程缓冲区里有1个,大儿子缓冲区里有1个,二儿子复制了父进程缓冲区里的1个,孙子复制了大儿子缓冲区里的1个
第二次fork后,printf之后,四个进程缓冲区都增加1个,共增加4个
这样一共就会打印8个

用gdb调试一下,验证猜想

ch3进程学习笔记

到这里,大儿子和孙子进程执行结束,将缓冲区的内容打印出来,一共打印4个,符合猜想

ch3进程学习笔记

到这里,二儿子进程执行结束,将缓冲区的内容打印出来,一共打印2个,符合猜想

ch3进程学习笔记

最后父进程执行结束,程序退出,将缓冲区的内容打印出来,一共打印2个,符合猜想

实践内容

fork

还有getpid()和getppid()

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 
  6 int main(){
  7     printf("before fork 1\n");
  8     printf("before fork 2\n");
  9     printf("before fork 3\n");
 10 
 11     pid_t pid = fork();
 12     if(pid == -1){
 13         perror("fork error");
 14         exit(1);
 15     }else if(pid ==0 ){
 16         printf("---child : my id = %d , myparent id = %d\n",getpid(),getppid());
 17     }else if(pid >0){
 18         printf("---parent : my child id = %d,my id = %d , my parent id = %d\n",pid,getpid(),getppid());
 19     }
 20     printf("=========EOF==========\n");
 21     return 0;
 22 }

ch3进程学习笔记

nfork

循环创建多个子进程
孤儿进程被孤儿院回收了,所以ppid = 1

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 
  6 int main(){
  7     int i;
  8     for(i=0;i<5;i++){
  9         if(fork()==0) break;
 10     }
 11     if(i==0){
 12         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 13     }else if(i==1){
 14         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 15     }
 16     else if(i==2){
 17         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 18     }
 19     else if(i==3){
 20         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 21     }
 22     else if(i==4){
 23         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 24     }else if(i==5){
 25         printf("I'm parent,my id = %d\n",getpid());
 26     }
 27     return 0;
 28 } 

ch3进程学习笔记

产生孤儿进程的原因就是子进程没有抢过父进程,父进程拿到CPU之后直接全部执行完事了。
解决:用sleep阻塞一下父进程即可。

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 
  6 int main(){
  7     int i;
  8     for(i=0;i<5;i++){
  9         if(fork()==0) break;
 10     }
 11     if(i==0){
 12         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 13     }else if(i==1){
 14         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 15     }
 16     else if(i==2){
 17         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 18     }
 19     else if(i==3){
 20         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 21     }
 22     else if(i==4){
 23         printf("I'm %dth child,my id = %d,my parent id = %d\n",i+1,getpid(),getppid());
 24     }else if(i==5){
 25         sleep(1);
 26         printf("I'm parent,my id = %d\n",getpid());
 27     }
 28     return 0;
 29 }

ch3进程学习笔记

execlp

默认在/bin/寻找可执行文件

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <pthread.h>
  4 #include <unistd.h>
  5 
  6 int main(){
  7     pid_t pid=fork();
  8     if(pid == -1){
  9         perror("fork error");
 10         exit(1);
 11     }else if(pid == 0){
 12         //child
 13         execlp("ls","ls","-l",NULL);
 14         perror("exec error");
 15         exit(1);
 16     }else if(pid > 0){
 17         //parent
 18         sleep(1);
 19         printf("I'm parent : %d\n",getpid());
 20     }
 21     return 0;
 22 }

ch3进程学习笔记

execl

将可执行文件的路径作为第一个参数传入

使用dup将执行结果重定向到./result.txt

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <pthread.h>
  4 #include <unistd.h>
  5 #include <fcntl.h>
  6 
  7 int main(){
  8     pid_t pid=fork();
  9     if(pid == -1){
 10         perror("fork error");
 11         exit(1);
 12     }else if(pid == 0){
 13         //child
 14         int fd = open("result.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
 15         if(fd<0){
 16             perror("open file error");
 17             exit(1);
 18         }
 19         dup2(fd,STDOUT_FILENO);
 20         execl("/bin/date","date",NULL);
 21         perror("exec error");
 22         exit(1);
 23     }else if(pid > 0){
 24         //parent
 25         sleep(1);
 26         printf("I'm parent : %d\n",getpid());
 27     }
 28     return 0;
 29 }

ch3进程学习笔记

wait

以及相关的宏函数

  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/wait.h>
  5 
  6 int main(){
  7     pid_t pid;
  8 
  9     pid = fork();
 10     if(pid==0){
 11         //child
 12         printf("child id = %d\n",getpid());
 13         sleep(10);
 14         printf("----------child die-----------\n");
 15         return 37;
 16     }else if(pid > 0){
 17         //parent
 18         int status;
 19         pid_t wpid = wait(&status);
 20         if(wpid==-1){
 21             perror("wait error");
 22             exit(1);
 23         }
 24         if(WIFEXITED(status)){
 25             //normally exit
 26             printf("child normally exit with %d\n",WEXITSTATUS(status));
 27         }else if(WIFSIGNALED(status)){
 28             //kill by signal
 29             printf("child is killed by signal %d\n",WTERMSIG(status));
 30         }
 31         printf("---------parent wait child %d finish--------\n",wpid);
 32     }else{
 33         perror("fork error");
 34     }
 35 
 36     return 0;
 37 }

ch3进程学习笔记

waitpid

用waitpid以非阻塞方式,回收多个进程中指定的子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
	int i;
	pid_t children[5];
	for(i=0;i<5;i++){
		pid_t pid = fork();
		if(pid==0){
			break;
		}else if(pid>0){
			children[i] = pid;
		}
	}
	if(i==5){
		//parent
		for(int j=0;j<5;j++){
			while(waitpid(children[j],NULL,WNOHANG)!=-1);
			printf("parent has waited child id = %d\n",children[j]);
		}
	}else{
		//children
		printf("I'm %dth child,id = %d\n",i,getpid());
	}
	return 0;
}

ch3进程学习笔记

pipe函数

用pipe实现man -k dir | grep 2

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {   
  8     int fd[2];
  9     if(pipe(fd)==-1){
 10         perror("pipe create error");
 11         exit(1);
 12     }
 13     
 14     pid_t pid = fork();
 15     if(pid == -1){
 16         perror("fork error");
 17         exit(1); 
 18     }else if(pid == 0){
 19         //child execute man -k
 20         close(fd[0]);           //向管道写入数据,关闭管道的读端
 21         dup2(fd[1],STDOUT_FILENO);
 22         execlp("man","man","-k","dir",NULL);
 23         perror("exec man -k error");
 24         exit(1);
 25     }else if(pid > 0){
 26         //parent execute grep
 27         close(fd[1]);           //从管道读出数据,关闭管道的写端
 28         dup2(fd[0],STDIN_FILENO);
 29         execlp("grep","grep","2",NULL);
 30         perror("exec man -k error");
 31         exit(1);
 32     }
 33     return 0;
 34 }

ch3进程学习笔记

ch3进程学习笔记

兄弟进程间使用pipe管道通信

依旧是实现 man -k dir | grep 2

注意关闭父进程的管道的两端

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <stdlib.h>
  5 #include <sys/wait.h>
  6 int main(){
  7     int fd[2];
  8     if(pipe(fd)==-1){
  9         perror("pipe create error");
 10         exit(1);
 11     }
 12     int i;
 13     for(i=0;i<2;i++){
 14         if(fork()==0) break;
 15     }   
 16     if(i == 2){
 17         //parent 既不读,也不写,pipe两端全关上
 18         close(fd[0]);
 19         close(fd[1]);
 20         wait(NULL);
 21         wait(NULL);
 22     }else if(i == 0){
 23         //child 0 execute man -k
 24         close(fd[0]);           //向管道写入数据,关闭管道的读端
 25         dup2(fd[1],STDOUT_FILENO);
 26         execlp("man","man","-k","dir",NULL);
 27         perror("exec man -k error");
 28         exit(1);
 29     }else if(i == 1){
 30         //child 1 execute grep
 31         close(fd[1]);           //从管道读出数据,关闭管道的写端
 32         dup2(fd[0],STDIN_FILENO);
 33         execlp("grep","grep","2",NULL);
 34         perror("exec man -k error");
 35         exit(1);
 36     }
 37     return 0;
 38 }

ch3进程学习笔记

命名管道

mkfifo函数

两个进程,分别进行读写操作

mkfifo

  2 #include <sys/stat.h>
  3 #include <stdio.h>
  4 int main(){
  5     if(mkfifo("myfifo",0664)==-1){
  6         perror("mkfifo error");
  7     }
  8     return 0;
  9 }

ch3进程学习笔记

读写fifo操作就是文件读写操作,就不上代码了

ch3进程学习笔记

上一篇:第三章学习笔记


下一篇:编写多线程的时候,最好不要再用fork函数,下面是一个多线程使用fork后的例子