一、进程的概念
1、什么是程序? 什么是进程?
程序:一堆待执行的代码。gcc hello.c -o hello 静态的文本数据
进程:当程序被CPU加载时,根据每一行代码做出相应的效果,形成动态的过程,那么这个过程就是进程。
其实说白了,进程就是一个正在执行的程序。
2、在linux下,如何开启一个新的进程?
直接在linux下,运行一个程序,就会开启相应的进程。
比如 ./hello --->开启名为 hello的进程。
3、 当进程开启之后,系统会为进程分配什么资源?
(1)会分配进程相应的内存空间
int x --->申请空间
(2)进程在系统中如何进行表示的?
学生管理系统 ---》每一名同学的信息是通过结构体去存储和管理
航班管理系统 --》每列航班信息 通过 结构体 去存储 和 管理
linux操作系统(linux内核)--》也是通过 结构体 去进行存储和管理
也就是说,当进程开启之后,系统会为这个进程分配一个任务结构体,这个任务结构体就是用来 描述这个进程。 struct task_struct{} --->进程控制块PCB。
结构体 : 进程ID号 、信号、文件、资源....
4、关于进程的命令
(1)查看进程的ID号
gec@ubuntu:~$ ps -ef --查看当前的进程
完整的指令是 ps -aux
可以看到它会展示很多进程。
但是,在实际工作中,我们会配合 grep 来查找程序中是否存在某一个进程(grep 起到一个类似管道过滤的作用):
(2) 查看整个linux系统进程之间关系(pstree)
(3) top ---以CPU占有率 进行排行 进程
使用 top 指令查看,类似Windows任务管理器,是动态的,会把相关的CPU,内存等信息显示出来,可以通过这个来评估看一个程序CPU占用情况以及内存消耗情况。
二、进程的状态
1、进程从诞生到死亡 会经历哪些状态 ?
就绪态: 等待CPU资源 不占用CPU资源 ,不运行代码
运行态: 占用CPU资源,运行代码。
暂停态: 占用CPU资源, 不运行代码
睡眠态: 占用CPU资源,不运行代码,可以回到就绪态。
僵尸态: 占用CPU资源,不运行代码 ,进程退出的时候一定会变成僵尸态。需要父进程回收
死亡态: 进程退出的时候,如果有人绑自己回收资源,那么僵尸态就会变成死亡态。释放资源
2、什么是僵尸态?
进程结束的时候,就会从运行变成 僵尸态,所谓的僵尸态,就是代表这个进程所占用的CPU资源和自己本身。
任务结构体没有被释放,这个状态的进程就是僵尸态进程。
三、进程的API接口
单进程程序 ---》只能一行行代码去执行
多进程程序---》同时执行两行代码 ---》产生一个子进程,帮自己去处理其他事情。
1、如何在一个正在执行的进程中产生一个子进程?(fork)
头文件
#include <unistd.h>
函数原型
pid_t fork(void);
函数作用
创建一个子进程,将父进程的资源复制一份,申请一片新的资源给子进程
返回值
成功:(一次调用,两次返回)
>0 父进程 ,返回的ID号表示的是子进程的ID号
==0 子进程
失败 -1
(1)例子1:
//fork01.c
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
int main()
{
printf("main\n");
//产生一个子进程
fork();
printf("after fork\n");
}
结果:
还有一种运行结果是父进程先退出来了,所以打印了命令提示符:
注意: 父进程退出之后,命令提示符就会显示出来
结论: 此时 父进程 比 子 进程 先 执行。实际上 父进程 与子进程 执行的先后次序是随机的。
(2)例子2:
父进程创建子进程之后,去处理其他事情:
//fork02.c
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
int main()
{
printf("main\n");
//产生一个子进程
pid_t id = fork();
/*一个是父进程 一个子进程*/
if(id == -1)
{
printf("fork error\n");
return -1;
}
else if(id > 0 )//父进程
{
sleep(1);//延时 1s,让子进程先执行
printf("我是你爹,我的儿子id号是:%d\n",id);
}
else if(id == 0)//子进程
{
printf("好大儿\n");
}
printf("after fork:%d\n",id);
}
总结:
1) 父子进程执行顺序是 随机的。
2)fork之后的代码,两个进程都会执行。
2、练习
(1)练习1:
写一个程序,使得子进程 每隔1s 就打印一次 hello ,父进程每隔2s 就打印一次world。
/*练习1: 写一个程序,使得子进程 每隔1s 就打印一次 hello ,父进程每隔2s 就打印一次world
*/
#include<stdio.h>
#include <unistd.h>
int main()
{
//创建子进程
pid_t id = fork();
if(id == -1)
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
while(1)
{
sleep(2);
printf("parent world\n");
}
}
else if(id == 0)//子进程
{
while(1)
{
sleep(1);
printf("\nchild hello\n");
}
}
return 0;
}
(2)练习2:
在一个进程中 打印 自己的进程ID号 和 父进程的ID号。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); ---》返回 当前 进程的ID号
pid_t getppid(void);---》返回 当前 进程的父进程ID号
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
//printf("main\n");
printf("main %d\n",getpid());
//产生一个子进程
pid_t id = fork();
/*一个是父进程 一个子进程*/
if(id == -1)
{
printf("fork error\n");
return -1;
}
else if(id > 0 )//父进程
{
sleep(1);//延时 1s,让子进程先执行
printf("父进程\n");
printf("%d %d\n",getpid(),getppid());
}
else if(id == 0)//子进程
{
printf("子进程\n");
printf("child:%d parent:%d\n",getpid(),getppid());
}
printf("after fork:%d\n",id);
}
四、孤儿进程 与 僵尸进程
孤儿进程 :一般情况下,调用fork()函数创建的子进程,父进程如果比子进程先退出,那么这个子进程称之为 孤儿进程。那么,祖先进程就会称为该进程 的 父进程 ,回收该子进程的资源。
僵尸进程:父进程还存在,但是去做别的事情了(比如在一个死循环里面,没有退出),此时子进程退出之后,就变成了僵尸进程。
1、僵尸进程的测试程序
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
//printf("main\n");
printf("main %d\n",getpid());
//产生一个子进程
pid_t id = fork();
/*一个是父进程 一个子进程*/
if(id == -1)
{
printf("fork error\n");
return -1;
}
else if(id > 0 )//父进程
{
while(1)
{
sleep(1);
printf("parent:%d 我正在忙其他事情\n",getpid());
}
}
else if(id == 0)//子进程
{
printf("child:%d 我是儿子,而且是好大儿\n",getpid());
}
}
结果:
通过ps -ef查看 ,当前 子进程 就变成了 僵尸进程 ,一直占用 系统资源,没有被回收
gec 3463 3462 0 00:24 pts/1 00:00:00 [a.out] <defunct>
2、孤儿进程的测试程序
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
/*
孤儿进程 :一般情况下,调用fork()函数创建的子进程,父进程如果比子进程先退出,那么这个子进程称之为 孤儿进程。那么,祖先进程就会成为该进程 的 父进程 ,回收该子进程的资源。
*/
int main()
{
//printf("main\n");
printf("main %d\n",getpid());
//产生一个子进程
pid_t id = fork();
/*一个是父进程 一个子进程*/
if(id == -1)
{
printf("fork error\n");
return -1;
}
else if(id > 0 )//父进程
{
printf("parent:%d 我挂了\n",getpid());
}
else if(id == 0)//子进程
{
sleep(1);
//延时1s之后,此时父进程已经退出了,子进程就变成了孤儿进程
//孤儿进程 在失去父亲之后会马上 寻找继父
printf("child:%d 我是儿子,而且是好大儿 等等我 %d\n",getpid(),getppid());
}
}
结果:
gec@ubuntu:/mnt/hgfs/gz2166/07-系统编程/01-进程的概念+进程API/1-code$ ./a.out
main 3480
parent:3480 我挂了
gec@ubuntu:/mnt/hgfs/gz2166/07-系统编程/01-进程的概念+进程API/1-code$ child:3481 我是儿子,而且是好大儿 等等我 1 (子进程的父进程)
3、练习
创建一个子进程 ,子进程打印hello后父进程打印world,之后 父进程 再创建另一个子进程 ,打印 end 。
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
/*
练习2:创建一个子进程 ,子进程打印hello后父进程打印world,之后 父进程 再创建另一个子进程 ,打印 end
*/
int main()
{
//产生一个子进程
pid_t id1 = fork();
if(id1 == -1)
{
printf("fork error\n");
return -1;
}
else if(id1 > 0 )//父进程
{
sleep(1);
printf("parent:%d world\n",getpid());
//父进程 再创建另一个子进程 ,打印 end
pid_t id2 = fork();
if(id2 == -1)
{
printf("fork error\n");
return -1;
}
else if(id2 > 0 )//父进程
{
sleep(1);
printf("parent end\n");
}
else if(id2 ==0 )//子进程2
{
printf("child2:%d end\n",getpid());
}
}
else if(id1 == 0)//子进程1
{
printf("child1:%d hello\n",getpid());
}
}
五、等待子进程退出 ,主动 回收 资源 (wait)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
函数作用: 等待一个进程退出,回收资源,也就是说 帮 这个子进程修改状态 (从僵尸态 变成 死亡态)
参数 :
status 子进程的退出状态 ,如果 父 进程 不关心 子进程 是怎么挂的,那么这个值可以设置成 NULL
返回值:
成功 返回 回收资源的那个子进程的ID号
失败 -1
注意:
1、wait函数 属于一个阻塞函数,如果子进程没有退出 变成 僵尸态 ,那么这个函数就会阻塞,直到子进程变成僵尸态之后,才会将子进程的资源回收
2、退出状态 不等于 退出值 , 退出状态 包括了 退出值,退出状态里面还有其他的
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
//产生一个子进程
pid_t id = fork();
if(id == -1)
{
printf("fork error\n");
return -1;
}
else if(id > 0 )//父进程
{
printf("我是父亲\n");
//父进程 阻塞 等待 子进程退出,回收子进程的资源
int status;
wait(&status);
}
else if(id == 0)//子进程
{
sleep(4);
printf("child:%d hello\n",getpid());
}
}
六、结束 当前进程(exit、_exit和_Exit)
#include <unistd.h>
#include <stdlib.h>
void exit(int status);
void _exit(int status);
void _Exit(int status);
函数作用: 让当前进程退出,并且可以设置自己的退出状态,传递给父进程。
参数:
status---》设置退出的状态值,可以让父进程获取到这个值
1、exit
exit - cause normal process termination
函数作用 : 先清洗缓冲区,再退出当前进程
2、 _exit和 _Exit
函数作用 :直接退出当前进程,不清洗缓冲区
1、例子1
通过调用exit 和 _exit退出 当前进程
exit:先清洗缓冲区,再退出 当前进程
_exit:直接退出当前进程
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
/*单进程的程序*/
printf("main ");
//先清洗缓冲区,再退出当前进程
//exit(0);//return 0;
//直接退出当前进程,不会帮助你清洗缓冲区
_exit(0);
printf("11112222\n");
}
2、例子2
获取子进程的退出值:
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
//创建一个子进程
pid_t id = fork();
if(id == -1)
{
}
else if(id >0)//父进程
{
printf("parent:%d\n",getpid());
}
else if(id ==0)//子进程
{
printf("child:%d\n",getpid());
exit(0);
//调用了exit,子进程后面的代码都不会执行了
printf("11111111111\n");
}
//下面的代码 子进程不会执行了,因为在子进程中已经调用了exit退出函数
//等待子进程退出,并且获取子进程的状态值
int status;
wait(&status);
//注意:status是 退出状态,里面包括了退出值
//调用系统已经封装好的宏函数实现 从 退出状态中 提取 出 退出值
if(WIFEXITED(status))//先判断子进程是否正常退出
{
//使用 WEXITSTATUS 宏函数 获取 子进程的退出值
printf("status:%d\n", WEXITSTATUS(status));
}
}
3、例子3
exit 跟 return 区别:
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
//例子3:exit 跟 return 区别
int func()
{
exit(0);//退出整个 进程
//return 0; //退出 当前 函数
}
int main()
{
/*单进程的程序*/
printf("main\n ");
//先清洗缓冲区,再退出当前进程
//exit(0);
func();
printf("end\n");
}
结论:
1、在main函数里面,return 和 exit 的作用都是一样的。
2、如果 是在某个函数的内部 ,return 表示退出当前函数 ,exit表示的是退出进程。
七、相关作业练习
1、作业1
用进程相关API函数编程一个程序,使之产生一个进程扇:父进程产生一系列子进程,每个子进程打印自己的PID然后退出。要求父进程最后打印PID。
#include<stdio.h>
#include<time.h>
#include<stdbool.h>
#include <unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
/*优化*/
int i = 0;
int ret = 0;
pid_t id;
for(i = 0; i<4;i++)
{
if((id = fork())== 0)
{
break;
}
}
if(id == 0)//子进程
{
printf("child%d:%d\n",i,getpid());
exit(1);
}
if(id > 0)//父进程
{
sleep(4);
wait(NULL);
}
printf("parent:%d\n",getpid());
}
2、作业2
用进程相关API函数编写一个程序,使之产生一个进程链:进程派生一个子进程后,父进程然后打印出自己的PID,然后退出,该子进程继续派生子进程,然后打印PID,然后退出,以此类推。
要求:
(1)实现一个父进程要比子进程先打印PID的版本。(即打印的PID一般是递增的)
(2)实现一个子进程要比父进程先打印PID的版本。(即打印的PID一般是递减的)
#include<stdio.h>
#include<time.h>
#include<stdbool.h>
#include <unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int i = 0;
int ret = 0;
pid_t pid;
for(i=0;i<4;i++)
{
pid = fork();
if(pid > 0)//父进程
{
wait(NULL);
printf("parent:%d\n",getpid());
break;
}
else if(pid == 0)//子进程
{
printf("child:%d\n",getpid());
}
}
}