01 学习目标
1.了解进程相关的概念
2.掌握fork/getpid/getppid函数的使用
3.熟练掌握ps/kill命令的使用
4.熟练掌握execl/execlp函数的使用
5.说出什么是孤儿进程什么是僵尸进程
6.熟练掌握wait函数的使用
7.熟练掌握wait函数的使用
8.熟练掌握waitpid函数的使用
02 进程和程序
什么是程序?
编译好的二进制文件
什么是进程?
运行着的程序。
站在程序员的角度:运行一系列指令的过程。
站在操作系统角度:分配系统资源的基本单位
区别:
- 程序占用磁盘,不占用系统资源
- 内存占用系统资源
- 一个程序对应多个进程,一个进程对应一个程序
- 程序没有生命周期,进程有生命周期
03 单道和多道程序设计
宏观上并行,微观上串行。
04 进程的状态转化
05 MMU的作用(Memory Management Unit)
中央处理器CPU:
内存管理单元MMU:
MMU的作用:
- 虚拟内存和物理内存的映射
- 修改内存访问级别
虚拟内存地址和物理地址存在对应关系:
用户空间映射到物理内存是独立的(相同的值在物理内存中也是不一致的值)。
06 PCB 的概念(Process Control Block)
PCB进程控制块
可以用如下命令查看struct task_struct 结构体定义:
sudo grep -rn "struct task_struct {" /usr/
sched.h->/usr/src/linuc-headers-4.8.0-46/include/linuc.sched.h
光标停留在{ 上,按%
其内部成员有很多,我们重点掌握以下部分即可:
- 进程id。系统中每个进程有唯一的id,在C语音中用pid_t类型表示,其实就是一个非负整数。
- 进程的状态,有就绪,运行,挂起,停止等状态。
- 进程切换时需要保存和恢复的一些CPU寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录(Current Working Directory)。
- umask掩码。
- 文件描述表,包含很多指向file结构体的指针。
- 和信号相关的信息。
- 用户id和组id。
- 会话(Session)和进程组。
- 进程可以使用的资源上限(Resource Limit)。
可以使用:ulimit -a 查看资源上限。
07 获取环境变量(了解)
环境变量:指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:
- 字符串(本质)
- 有统一格式:名=值[:值]
- 值用来描述进程环境信息。
常见的环境变量
- PATH
$ echo $PATH
-
SHELL
当前Shell,它的值通常是/bin/bash. -
TERM
当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。 -
LANG
语言和locale,决定了字符编码以及时间,货币等信息的显示格式。 -
HOME
当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。
如下:
getenv函数(掌握)
getenv 获取环境变量值
char * getenv(const char* name)
- 成功:返回环境变量的值;失败:NULL(name不存在)
setenv函数
setenv 设置环境变量的值
int setenv(const char* name,const char* value,int overwrite)
-
overwrite:
1:覆盖原环境变量
0:不覆盖.(该参数常用于设置新环境变量,如:ABC = haha-day-night) -
返回值:0成功,-1失败。
unsetenv函数
unsetenv删除环境变量name的定义
int unsetenv(const char* name)
- 注意事项:name不存在仍返回0(存在),当name命令为“ABC=”时则会出错。
- 返回值:0成功,-1失败。
命令行设置变量:
export key=val;
.bashrc
08 进程控制
1.fork函数
fork 创建一个新的进程。
pid_t fork(void);
- 返回值
失败-1
成功,两次返回。父进程返回的子进程的id,子进程返回的0.
2.getpid函数
getpid获得pid,进程id,获得当前进程
pid_t getpid(void)
3.getppid函数
getppid获得当前进程父进程的id。
pid_t getppid(void);
4.fork创建子进程
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("Begin...\n);
pid_t pid = fork();
if(pid<0)
{
perror("fork err");
exit(1);
}
if(pid == 0)
{
//子进程
printf("I am a child,pid = %d,ppid=%d\n",getpid(),getppid());
}
else if(pid > 0)
{
//父进程的逻辑
printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid());
}
printf("End....\n");
return 0;
}
此时子线程的ppid=1,是因为父进程已经结束。
从最后一份End…在命令行可知。
为解决上述问题:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("Begin...\n);
pid_t pid = fork();
if(pid<0)
{
perror("fork err");
exit(1);
}
if(pid == 0)
{
//子进程
printf("I am a child,pid = %d,ppid=%d\n",getpid(),getppid());
}
else if(pid > 0)
{
//父进程的逻辑
printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid());
sleep(1);
}
printf("End....\n");
return 0;
}
效果如同输入:vsp fork.c
fork:
fork代码执行分叉:
5.进程控制命令
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("Begin...\n);
pid_t pid = fork();
if(pid<0)
{
perror("fork err");
exit(1);
}
if(pid == 0)
{
//子进程
printf("I am a child,pid = %d,ppid=%d\n",getpid(),getppid());
while(1)
{
printf("I am a child\n");
sleep(1);
}
}
else if(pid > 0)
{
//父进程的逻辑
printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid());
while(1)
{
sleep(1);
}
}
printf("End....\n");
return 0;
}
查看进程信息
ps
- ps auj
- ps ajx – 可以追述进程之间的血缘关系
kill
- 给进程发送一个信号
- SIGKILL 9号信号
- kill -SIGKILL pid --杀死进程
6.创建n个子进程
nfork.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int n=5;
int i=0;
pid_t pid =0;
for(i=0;i<5;i++)
{
if(pid ==0)
{
//son
printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
}
else if(pid>0)
{
//father
printf("I am father,pid=%d,ppid=%d\n",getpid(),getppid());
}
}
while(1)
{
sleep(1);
}
return 0;
}
2^5=32个
以上代码不符合创建n个,裂变,创建n个以下代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int n=5;
int i=0;
pid_t pid =0;
for(i=0;i<5;i++)//父进程循环结束
{
if(pid ==0)
{
//son
printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
break;//子进程退出循环的接口
}
else if(pid>0)
{
//father
printf("I am father,pid=%d,ppid=%d\n",getpid(),getppid());
}
}
while(1)
{
sleep(1);
}
return 0;
}
父进程1个,子进程5个,共6个。
7.循环创建n个子进程控制顺序
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int n=5;
int i=0;
pid_t pid =0;
for(i=0;i<5;i++)//父进程循环结束
{
if(pid ==0)
{
//son
printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
break;//子进程退出循环的接口
}
else if(pid>0)
{
//father
//printf("I am father,pid=%d,ppid=%d\n",getpid(),getppid());
}
}
sleep(i);//控制子进程输出顺序
if(i<5)
{
printf("I am child,will exit,pid=%d,ppid=%d\n",getpid(),getppid());
}
else
{
printf("I am parent,will out exit,pid=%d,ppid=%d\n",getpid(),getppid());
}
return 0;
}
09 父子进程共享的内容
刚fork后,父子进程有哪些相同:
全局变量,.data,.text,栈,堆,环境变量,用户ID,宿主目录,进程工作目录,信号处理方式…
刚fork后,父子进程有哪些不同:
1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容。
父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程逻辑还是执行自己逻辑都能节省内存开销。
10 父子进程不共享全局变量
shared.c
#include<stdio.h>
#include<unistd.h>
int var = 100;
int main()
{
pid_t pid = fork();
if(pid==0)
{
//son
printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
var = 1001;
printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
}
else if(pid>0)
{
//parent
sleep(1);//保障子进程能够修改
printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid());
}
return 0;
}
子进程修改全局变量,父进程并没有被修改。
shared.c
#include<stdio.h>
#include<unistd.h>
int var = 100;
int main()
{
pid_t pid = fork();
if(pid==0)
{
//son
printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
var = 1001;
printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
sleep(3);
printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
}
else if(pid>0)
{
//parent
sleep(1);//保障子进程能够修改
printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid());
var =2000;
printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid());
}
return 0;
}
父进程修改全局变量,子进程也没有被影响。
11 exec函数族
execl 执行其他程序
int execl(const char* path,const char* arg,…/*(char *)NULL */);
execlp 执行程序的时候,使用PATH环境变量,执行的程序可以不用加路径
int execlp(const char* file,const char* arg,…/*(char *)NULL */);
-
file 要执行的程序
-
arg 参数列表
参数列表最后需要一个NULL作为结尾,哨兵 -
返回值
只有失败才返回
使用原理其实就是其他程序的text替换自己的text,如下:
execlp.c
#include<stdio.h>
int main()
{
//execlp(const char *_file,const char *_arg,...)
//因为参数是从0开始的,第二个ls事占位0.
execlp("ls","ls","-1","--color=auto",NULL);
//不需要判断返回值
perror("exec err");
return 0;
}
execl.c
#include<stdio.h>
int main()
{
//execlp(const char *_file,const char *_arg,...)
//因为参数是从0开始的,第二个ls事占位0.
execl("/bin/ls","ls","-1","--color=auto",NULL);
//不需要判断返回值
perror("exec err");
return 0;
}
1.exec族用法
char *const ps_argv[]={"ps","-o","pid,ppid,pgrp,session,comm",NULL};
char const ps_envp[]={"PATH=/bin/usr/bin","TERM=console",NULL};
//1.execl
execl("/bin/ps","ps","-o","pid,ppid,pgrp,session,comm",NULL);
//2.execv
execv("/bin/ps",ps_argv);
//3.execle
execle("/bin/ps","ps","-o","pid,ppid,pgrp,session,comm",NULL,ps_envp);
//4.execlp
execlp("ps","ps","-o","pid,ppid,pgrp,session,comm",NULL);
//5.execvp
execvp("ps",ps_argv);
2.exec函数族
3.exec实现自定义程序
shell终端就是调用fork()。
12 孤儿进程与僵尸进程
1.孤儿进程
孤儿进程:父亲死了,子进程被init进程领养
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
while(1)
{
printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}
else if(pid>0)
{
printf("I am parent,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(5);
printf("I am parent,I will die!\n");
}
return 0;
}
因为子线程被init领养了,无法ctrl+c退出,使用kill杀死进程退出。
kill -9 4965
2.僵尸进程
僵尸进程:子进程死了,父进程没有回收子进程的资源(PCB)
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(2);
printf("I am child,I will die!\n");
}
else if(pid>0)
{
while(1)
{
printf("I am parent,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}
Z+ :僵尸进程
僵尸进程无法杀死掉的子线程。
如何回收僵尸进程:杀死父亲,init领养,负责回收。
13 wait函数简单使用和说明
wait 回收子进程,知道子进程的死亡原因(僵尸进程的回收)。
作用:
- 阻塞等待
- 回收子进程资源
- 查看死亡原因
pid_t wait(int * status)
- status 传出参数
- 返回值
成功返回终止的子进程ID
失败,返回-1
子进程的死亡原因:
- 正常死亡WIFEXITED
如果WIFEXITED为真,使用WEXITSTATUS得到退出状态 - 非正常死亡WIFSIGNALED
如果WIFSIGNALED为真,使用WTERMSIG得到信号(杀死子线程的信号)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
printf("I am child,will die!\n");
sleep(2);
}
else if(pid>0)
{
printf("I am parent,wait for child die!");
pid_t wpid = wait(NULL);
printf("wait ok,wpid=%d,pid=%d\n",wpid,pid);
while(1)
{
sleep(1);
}
}
return 0;
}
14 wait回收并且查看死亡原因
正常死亡:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
printf("I am child,will die!\n");
sleep(2);
//return 101;//第一种退出方式
exit(102);//第二种退出方式
}
else if(pid>0)
{
printf("I am parent,wait for child die!");
int status;
pid_t wpid = wait(&status);
printf("wait ok,wpid=%d,pid=%d\n",wpid,pid);
if(WIFEXITED(status))
{
printf("child exit with %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("child killed by %d\n",WTERMSIG(status));
}
while(1)
{
sleep(1);
}
}
return 0;
}
非正常死亡:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
printf("I am child,will die!\n");
sleep(2);
while(1)
{
printf("I am child,guo lai da wo!\n");
sleep(1);
}
//return 101;//第一种退出方式
exit(102);//第二种退出方式
}
else if(pid>0)
{
printf("I am parent,wait for child die!");
int status;
pid_t wpid = wait(&status);
printf("wait ok,wpid=%d,pid=%d\n",wpid,pid);
if(WIFEXITED(status))
{
printf("child exit with %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("child killed by %d\n",WTERMSIG(status));
}
while(1)
{
sleep(1);
}
}
return 0;
}
命令行输入:kill -9 5814(pid)
也可以输入:kill 5814,此时的信号是15
15 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
- 如果设置了WNOHANG,那么如果子进程退出,返回退出的pid
- 失败返回-1(没有子进程)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid = fork();
if(pid==0)
{
printf("I am child,pid=%d\n",getpid());
sleep(2);
}
else if(pid>0)
{
printf("I am parent,pid=%d\n",getpid());
int ret;
while((ret=waitpid(-1,NULL,WNOHANG))==0)
{
sleep(1);
}
printf("ret=%d\n",ret);
ret = waitpid(-1,NULL,WNOHANG);
if(ret<0)
{
perror("wait err");
}
while(1)
{
sleep(1);
}
}
}
16 用wait回收多个子进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
int n=5;
int i=0;
pid_t pid;
for(i=0;i<5;i++)
{
pid=fork();
if(pid==0)
{
printf("I am child,pid=%d\n",getpid());
break;
}
}
sleep(i);
if(i==5)
{
for(i=0;i<5;i++)
{
pid_t wpid=wait(NULL);
printf("wpid=%d\n",wpid);
}
while(1)
{
sleep(1);
}
}
return 0;
}
17 waitpid回收多个子进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
int n=5;
int i=0;
pid_t pid;
for(i=0;i<5;i++)
{
pid=fork();
if(pid==0)
{
break;
}
}
if(i==5)
{
//parent
printf("I am parent!");
//如何使用waitpid回收?-1 代表子进程都死了,都收了
while(1)
{
pid_t wpid=wiatpid(-1,NULL,WNOHANG);
if(wpid==-1)
{
break;
}
else if(wpid>0)
{
printf("waitpid wpid =%d\n",wpid);
}
}
while(1)
{
sleep(1);
}
}
if(i<5)
{
printf(:I am child,i=%d,pid=%d\n",i,getpid());
}
return 0;
}
18 作业
1.创建子进程,调用fork之后,在子进程调用自定义程序(段错误,浮点型错误),用waitpid回收,查看退出状态。
fpe.c:
#include<stdio.h>
#include<unistd.h>
int main()
{
int a =10;
int b =a/0;
return 0;
}
直接运行效果:
execl.c:
#include<stdio.h>
#include<unistd.h>
int main()
{
execl("./fpe","fpe",NULL);
printf("bye bye!\n");
return 0;
}
2.验证子进程是否共享文件描述符,子进程负责写入数据,父进程负责读数据。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("./a.out filename\n");
return -1;
}
int fd = open(argv[1],O_RDWR);
if(fd<0)
{
perror(“open err");
exit(1);
}
pid_t pid = fork();
if(pid==0)
{
//son
write(fd,"hello\n”,6);
}
else if(pid>0)
{
//parent
sleep(1);
write(fd,"world\n”,6);
sleep(NULL);
}
return 0;
}