Linux系统编程——进程控制
在学习Linux系统编程总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
09-linux-day05(进程控制)
目录:
一、学习目标
二、进程
1、进程和程序
2、单道和多道程序设计
3、进程的状态转化
4、MMU 的作用
5、PCB的概念
6、获取环境变量
7、进程控制函数fork
8、fork创建子进程
9、进程控制的命令
10、创建n个子进程
11、循环创建n个子进程控制顺序
12、父子进程共享的内容
13、父子进程不共享全局变量
14、execlp函数介绍
15、exec函数规律
16、exel实现自定义程序
17、孤儿进程与僵尸进程
18、wait函数简单使用和说明
19、wait回收并且查看死亡原因
20、waitpid回收子进程
21、用wait回收多个子进程
22、waitpid回收多个子进程
一、学习目标
1、了解进程相关的概念
2、掌握fork/getpid/getppid函数的使用
3、熟练掌握ps/kill命令的使用
4、熟练掌握execl/execlp函数的使用
5、说出什么是孤儿进程什么是僵尸进程
6、熟练掌握wait函数的使用
7、熟练掌握waitpid函数的使用
二、进程
1、进程和程序
什么是程序?编译好的二进制文件。
什么是进程?运行着的程序。
站在程序员的角度:运行一系列指令的过程。
站在操作系统角度:分配系统资源的基本单位。
区别:
程序占用磁盘,不占用系统资源。
进程占用系统资源。
一个程序对应多个进程,一个进程对应一个程序。
程序没有生命周期,进程有生命周期。
2、单道和多道程序设计
3、进程的状态转化
进程的状态切换
4、MMU 的作用
》MMU作用:1)虚拟内存和物理内存的映射;2)修改内存访问级别
用户空间映射到物理内存是独立的。
5、PCB的概念
Linux内核的进程控制块是task_struct结构体。
查找结构体:
> sudo grep -rn "struct task_struct {" /usr/
技巧:光标停留在{上,按%,可以到结构体的结尾。(400多行)
查看进程的资源上限:
>ulimit -a
6、获取环境变量
查看所有的环境变量:(写法:key=value,且等号两端不能有空格)
>env
常见的环境变量:
>echo $HOME
>echo $PATH
》getenv函数——获取环境变量
man 2 getenv
char *getenv(const char *name);
>touch getenv.c
>vi getenv.c
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int main() 5 { 6 printf("homepath is [%s]\n", getenv("HOME")); 7 return 0; 8 }
>make
>./getenv
》setenv函数——设置环境变量的值
man 2 setenv
int setenv(const char *name, const char *value, int overwrite);
参数overwrite取值:1-覆盖原环境变量;0-不覆盖(该参数常用于设置新环境变量。)
一般配置(.bashrc)文件!(如:export key=val;)
》unsetenv函数——删除环境变量name的定义
man 2 unsetenv
int unsetenv(const char *name);
注意事项:name不存在仍返回0(成功),当name命名为“ABC=”时则会出错!
7、进程控制函数fork
>touch fork.c
>vi fork.c
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 printf("Begin ....\n");//此处缺少“\n”结果会不一样,这是由于printf有缓冲区机制,往屏幕上输出,会有行缓冲 7 pid_t pid = fork(); 8 printf("End ....\n"); 9 return 0; 10 }
>make
>./fork
(可以知道结束了两个进程。)
》getpid获得当前进程的PID和进程ID,,getppid——获得当前进程父进程的ID
man getpid
pid_t getpid(void);
pid_t getppid(void);
8、fork创建子进程
>vi fork.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 printf("Begin ....\n"); 8 pid_t pid = fork(); 9 if(pid < 0){ 10 perror("fork err"); 11 exit(1); 12 } 13 if(pid == 0){ 14 //子进程 15 printf("I am a child,pid = %d,ppid = %d\n",getpid(), getppid()); 16 } 17 else if(pid > 0){ 18 //父进程的逻辑 19 printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid()); 20 sleep(1);//让父进程等待一段时间,再死去。 21 } 22 23 printf("End ....\n"); 24 return 0; 25 }
>make
>./fork
9、进程控制的命令
>vi fork.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 printf("Begin ....\n"); 8 pid_t pid = fork(); 9 if(pid < 0){ 10 perror("fork err"); 11 exit(1); 12 } 13 if(pid == 0){ 14 //子进程 15 printf("I am a child,pid = %d,ppid = %d\n",getpid(), getppid()); 16 while(1){ 17 printf("I am a child\n"); 18 sleep(1); 19 } 20 } 21 else if(pid > 0){ 22 //父进程的逻辑 23 printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid()); 24 while(1){ 25 sleep(1); 26 } 27 } 28 29 printf("End ....\n"); 30 return 0; 31 }
>make
>./fork
让子进程一直保持运行,打开另一个终端查看PPID和PID分析:
》ps aux——查看进程相关信息的指令
》ps ajx——可以查看更多进程相关的信息,从而追溯进程之间的血缘关系
可以看到PPID、PID,可以看出shell进程是所有进程的父亲,继续追溯,可以看到最终父进程是init
(init进程是所有进程的祖先!)
>kill -9 2890
>ps ajx
杀死了父进程,init成为子进程2891新的父亲。
>kill 2891
杀死子进程
》kill指令——给进程发送一个信号
kill -l 查看信号相关的信息
SIGKILL 9号信号
kill -9 pid——杀死进程
10、创建n个子进程
需求:让父进程创建n个子进程
>touch nfork.c
>vi nfork.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 int n = 5; 8 int i = 0; 9 pid_t pid = 0; 10 for(i = 0; i < 5; i++){//父进程循环结束 11 pid = fork(); 12 if(pid == 0){ 13 //son 14 printf("I am child, pid=%d,ppid=%d\n",getpid(),getppid()); 15 //break;//子进程退出循环的接口 16 } 17 else if(pid > 0){ 18 //father 19 printf("I am father, pid=%d,ppid=%d\n",getpid(),getppid()); 20 } 21 } 22 while(1){ 23 sleep(1); 24 } 25 return 0; 26 }
>gcc nfork.c
>./a.out
>ps aux | grep a.out |grep -v grep | wc -l
(创建了32个进程!)
11、循环创建n个子进程控制顺序
》需求:精确控制各个子进程
>touch nfork1.c
>vi nfork1.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 int n = 5; 8 int i = 0; 9 pid_t pid = 0; 10 for(i = 0; i < 5; i++){//父进程循环结束 11 pid = fork(); 12 if(pid == 0){ 13 //son 14 printf("I am child, pid=%d,ppid=%d\n",getpid(),getppid()); 15 break;//子进程退出循环的接口 16 } 17 else if(pid > 0){ 18 //father 19 //printf("I am father, pid=%d,ppid=%d\n",getpid(),getppid()); 20 } 21 } 22 23 sleep(i); 24 if(i < 5){ 25 printf("I am child, will exit,pid=%d,ppid=%d\n",getpid(),getppid()); 26 } 27 else{ 28 //father 29 printf("I am parent, will out,pid=%d,ppid=%d\n",getpid(),getppid()); 30 } 31 32 return 0; 33 }
>gcc nfork1.c
>./a.out
(创建了5个进程!)
12、父子进程共享的内容
13、父子进程不共享全局变量
>touch shared.c
>vi shared.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int var = 100; 6 7 int main() 8 { 9 pid_t pid = fork(); 10 if(pid == 0){ 11 //son 12 printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid()); 13 var = 1001; 14 printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid()); 15 } 16 else if(pid > 0){ 17 //parent 18 sleep(1);//保证子进程能够修改var的值成功 19 printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid()); 20 } 21 22 return 0; 23 }
(让子进程改,看结果)
>gcc shared.c
>./a.out
(让父进程改,看结果)
>vi shared.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int var = 100; 6 7 int main() 8 { 9 pid_t pid = fork(); 10 if(pid == 0){ 11 //son 12 printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid()); 13 var = 1001; 14 printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid()); 15 sleep(3); 16 printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid()); 17 } 18 else if(pid > 0){ 19 //parent 20 sleep(1);//保证子进程能够修改var的值成功 21 printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid()); 22 var = 2000; 23 printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid()); 24 } 25 26 return 0; 27 }
>gcc shared.c
>./a.out
》结论:即使是全局变量,父子进程也不共享。(读时共享,写时复制。)
14、execlp函数介绍
>man execl
》execl作用:执行其他程序
int execl(const char *path,const char *arg,...) /*(char *)NULL*/
》exelp作用:执行程序的时候,使用PATH环境变量,执行的程序可以不用加路径
int execlp(const char *file,const char *arg,...) /*(char *)NULL */
file 要执行的程序
arg 参数列表
参数列表最后需要一个NULL作为结尾(哨兵)
返回值:只有失败才返回
>touch execl.c
>vi execl.c
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 //int execlp(const char *file,const char *arg,...) /*(char *)NULL */ 7 execlp("ls", "ls", "-l", "--color=auto",NULL);//如果使其执行错误,返回,把第一个ls改为非法的lsxxx 8 //不需要判断返回值 9 perror("exec err"); 10 return 0; 11 }
>gcc execl.c
>./a.out
>vi execl.c
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 //int execlp(const char *file,const char *arg,...) /*(char *)NULL */ 7 //execlp("ls", "ls", "-l", "--color=auto",NULL); 8 execl("/bin/ls", "ls", "-l", "--color=auto",NULL); 9 //不需要判断返回值 10 perror("exec err"); 11 printf("hello\n"); 12 return 0; 13 }
>gcc execl.c
>./a.out
15、exec函数规律
16、exel实现自定义程序
>touch fpe.c
>vi fpe.c
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 int a = 10; 7 int b = a/0; 8 return 0; 9 }
>make
>./fpe
(报错:浮点数例外(核心已转储))
>touch execl.c
>vi execl.c
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int main() 5 { 6 execl("./fpe", "fpe", NULL); 7 8 printf("bye bye!\n"); 9 return 0; 10 }
>gcc execl.c
>./a.out
(报错:浮点数例外(核心已转储))
17、孤儿进程与僵尸进程
》孤儿进程:父亲死了,子进程被init进程领养。
》僵尸进程:子进程死了,父进程没有回收子进程的资源(PCB)。
>touch orphan.c
>vi orphan.c
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 pid_t pid = fork(); 7 if(pid == 0){ 8 //子进程 9 while(1){ 10 printf("I am a child,pid=%d,ppid=%d\n",getpid(), getppid()); 11 sleep(1); 12 } 13 } 14 else if(pid > 0){ 15 //父进程的逻辑 16 printf("I am parent,pid=%d,ppid=%d\n",getpid(),getppid()); 17 sleep(5); 18 printf("I am parent,I will die!\n") 19 } 20 21 printf("End ....\n"); 22 return 0; 23 }
>gcc orphan.c
>./a.out
(Ctrl+c无法结束子进程,因为子进程脱离shell,只能打开另一个终端,ps ajx查看pid,然后用kill -9 pid杀死)
>touch zoombie.c
>vi zoombie.c
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 pid_t pid = fork(); 7 if(pid == 0){ 8 //子进程 9 printf("I am child,pid=%d,ppid=%d\n",getpid(), getppid()); 10 sleep(2); 11 printf("I am child,I will die\n"); 12 } 13 else if(pid > 0){ 14 //父进程的逻辑 15 while(1){ 16 printf("I am parent,pid=%d\n",getpid()); 17 sleep(1); 18 } 19 } 20 return 0; 21 }
>gcc orphan.c
>./a.out
(打开另一个终端查看 >ps aux | grep a.out)
(如果想研究下,使用man ps查看,然后用/zombie定位到)
》如何回收僵尸进程资源?
杀死父亲,init领养,负责回收。
18、wait函数简单使用和说明
》wait作用:1)阻塞等待;2)回收子进程资源;3)知道子进程的死亡原因
man 2 wait
pid_t wait(int *status);
status 传出参数
返回值:成功返回终止的子进程ID,失败返回-1
子进程的死亡原因:
正常死亡 WIFEXITED
如果WIFEXITED为真,使用WEXITSTATUS得到退出状态。
非正常死亡WIFSIGNALED
如果WIFSIGNALED为真,使用WTERMSIG得到信号
19、wait回收并且查看死亡原因
>touch wait.c
>vi wait.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<sys/types.h> 4 #include<sys/wait.h> 5 6 int main() 7 { 8 pid_t pid = fork(); 9 if(pid == 0){ 10 //子进程 11 printf("I am child,I will die\n"); 12 sleep(2); 13 while(1){//迁怒,等待kill杀死,非正常死亡 14 printf("I am child,guo lai da wo!\n"); 15 sleep(1); 16 } 17 //正常死亡的两种情况 18 //return 101; 19 //exit(101); 20 } 21 else if(pid > 0){ 22 //父进程的逻辑 23 printf("I am parent,wait for child die!\n"); 24 int status; 25 26 pid_t wpid = wait(&status); 27 printf("wait ok,wpid=%d,pid=%d\n",wpid,pid); 28 if(WIFEXITED(status)){ 29 printf("child exit with %d\n",WEXITSTATUS(status)); 30 } 31 if(WIFSIGNALED(status)){ 32 printf("child killed by %d\n",WTERMSIG(status)); 33 } 34 while(1){ 35 sleep(1); 36 } 37 } 38 return 0; 39 }
>gcc wait.c
>./a.out
(打开另一个终端,使用ps aux|grep a.out查看pid,然后用kill -9 pid杀死子进程,原终端将会收到:child killed by 9;用kill pid杀死子进程,原终端将会收到:child killed by 15)
20、waitpid回收子进程
man waitpid
pid_t waitpid(pid_t pid, int *status, int options);
pid
<-1 组id
1 回收任意
0 回收和调用进程组id相同组内的子进程
>0 回收指定的pid
options
0与wait相同,也会阻塞
WNOHANG 如果当前没有子进程退出的,会立刻返回
返回值
如果设置了WNOHANG,那么如果没有子进程退出,返回0
如果有子进程退出,返回退出的pid
失败返回-1(没有子进程)
>touch waitpid.c
>vi waitpid.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<sys/types.h> 4 #include<sys/wait.h> 5 #include<stdlib.h> 6 7 int main() 8 { 9 pid_t pid = fork(); 10 if(pid == 0){ 11 //子进程 12 printf("I am child,pid=%d\n",getpid()); 13 sleep(2); 14 } 15 else if(pid > 0){ 16 //父进程的逻辑 17 printf("I am parent,pid=%d\n",getpid()); 18 19 int ret; 20 while((ret = = waitpid(-1,NULL,WNOHANG)) == 0){ 21 sleep(1); 22 } 23 printf("ret = %d\n",ret); 24 ret = waitpid(-1,NULL,WNOHANG); 25 if(ret < 0){ 26 perror("wait err"); 27 } 28 while(1){ 29 sleep(1); 30 } 31 } 32 return 0; 33 }
>gcc waitpid.c
>./a.out
21、用wait回收多个子进程
>touch nfork_wait.c
>vi nfork_wait.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<sys/types.h> 4 #include<sys/wait.h> 5 #include<stdlib.h> 6 7 int main() 8 { 9 int n = 5; 10 int i = 0; 11 pid_t pid; 12 for(i = 0; i < 5; i++){ 13 pid = fork(); 14 if(pid == 0){ 15 printf("I am child,pid=%d\n",getppid()); 16 break; 17 } 18 } 19 sleep(i); 20 if(i == 5){ 21 for(i = 0; i < 5; i++){ 22 pid_t wpid = wait(NULL); 23 printf("wpid = %d\n",wpid); 24 } 25 while(1){ 26 sleep(1); 27 } 28 } 29 return 0; 30 }
>gcc nfork_wait.c
>./a.out
(打开另一个终端,使用ps aux|grep a.out查看pid,看回收是否成功)
22、waitpid回收多个子进程
>touch nfork_waitpid.c
>vi nfork_waitpid.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<sys/types.h> 4 #include<sys/wait.h> 5 #include<stdlib.h> 6 7 int main() 8 { 9 int n = 5; 10 int i = 0; 11 pid_t pid; 12 for(i = 0; i < 5; i++){ 13 pid = fork(); 14 if(pid == 0){ 15 printf("I am child,pid=%d\n",getppid()); 16 break; 17 } 18 } 19 if(i == 5){ 20 //parent 21 printf("I am parent!\n"); 22 //如何使用waitpid回收?-1代表子进程都死了,都回收了 23 while(1){ 24 pid_t wpid = waitpid(-1, NULL, WNOHANG); 25 if(wpid == -1){ 26 break; 27 } 28 else if(wpid > 0){ 29 printf("waitpid wpid=%d\n",wpid); 30 } 31 } 32 while(1){ 33 sleep(1); 34 } 35 } 36 if(i < 5){ 37 //sleep(i); 38 printf("I am child,i = %d,pid=%d\n",i,getpid()); 39 } 40 return 0; 41 }
>gcc nfork_waitpid.c
>./a.out
(打开另一个终端,使用ps aux|grep a.out查看pid,看回收是否成功)
》作业
>touch fork_write.c
>vi fork_write.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<sys/types.h> 4 #include<sys/wait.h> 5 #include<stdlib.h> 6 #include<fcntl.h> 7 #include<string.h> 8 9 int main(int argc,char *argv[]) 10 { 11 if(argc != 2){ 12 printf("./a.out filename\n"); 13 return -1; 14 } 15 int fd = open(argv[1],O_RDWR); 16 if(fd < 0){ 17 perror("open err"); 18 exit(1); 19 } 20 pid_t pid = fork(); 21 if(pid == 0){ 22 //son 23 write(fd,"hello\n",6); 24 } 25 else if(pid > 0){ 26 //parent 27 sleep(1); 28 write(fd,"world\n",6); 29 wait(NULL); 30 } 31 32 return 0; 33 }
>touch 11
>make
>ls -lrt
>./fork_write 11
>cat 11
1 hello
2 world
(没有覆盖,说明写时共享文件描述符,读写位置一样,一个进程写了,读写位置改变了,另一个进程受影响,所以可以跟着写。)
在学习Linux系统编程总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。
posted on 2020-06-30 16:30 Alliswell_WP 阅读(346) 评论(0) 编辑 收藏 举报