进程相关的概念
程序——编译好的二进制文件
进程——运行着的程序
站在程序员的角度——运行一系列指令的过程
站在操作系统的角度——分配系统资源的基本单位
区别
- 程序占用磁盘,不占用系统资源
- 内存占用系统资源
- 一个程序对应多个进程,一个进程对应一个程序
- 程序没有生命周期,进程有生命周期
单道程序和多道程序
多道——宏观上并行,微观上串行。
进程的状态转化
MMU的作用
1、虚拟地址和物理内存之间实现映射
2、修改内存访问级别
映射关系
进程控制块PCB
查找PCB这个结构体在哪
sudo grep -rn "struct task_struct{" /usr/
PCB中包含的内容
查看所有文件设置
ulimit -a
环境变量
查看所有环境变量
env
查看单个环境变量
echo $PATH
获取环境变量
char *getenv(const char *name);
#include<stdio.h>
#include<stdlib.h>
int main(){
printf("homepath is [%s]\n", getenv("HOME"));
return 0;
}
运行结果:
fork函数——创建新的进程
pid_t fork(void);
获得pid, 进程ID, 获得当前进程
pid_t getpid(void);
获得当前进程的父进程id
pid_t getppid(void);
使用fork(),getpid(),getppid()的测试用例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
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("child=%d, self=%d, ppid=%d\n", pid, getpid(), getppid());
}
return 0;
}
运行结果(子进程的父进程pid号为1,因为父进程先死了,子进程成了孤儿)
进程控制命令
查看进程信息
ps aux
查看进程的进程组的信息
ps ajx
kill的各种信息
通过
kill -l
可以获取kill的详细信息
创建n个子进程
查看进程a.out有几个
ps -aux | grep a.out | grep -v grep | wc -l
-v 排除某些信息
wc -l 统计数量
对于下面的代码,会产生32个子进程(原计划产生5个子进程)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
pid_t pid=0;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) {
printf("I am child, pid=%d, ppid=%d", getpid(), getppid);
}
else if (pid > 0) {
printf("I am father, pid=%d, ppid=%d", getpid(), getppid);
}
}
while (1) {
sleep(1);
}
return 0;
}
原理如图:(32=2的5次幂)
产生5个的方法(判断出来是子进程就跳出循环)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
pid_t pid=0;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) {
printf("I am child, pid=%d, ppid=%d", getpid(), getppid);
break;
}
else if (pid > 0) {
printf("I am father, pid=%d, ppid=%d", getpid(), getppid);
}
}
while (1) {
sleep(1);
}
return 0;
}
让进程按创建的顺序退出,父进程最后退出
子进程被创建出来之后,break了,跳出了循环,继续执行循环下面的语句。我们可以根据此时的 i 的值来判断这是第几个被创建出来的子进程。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
int i = 0;
pid_t pid=0;
for ( i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) {
printf("I am child, pid=%d, ppid=%d", getpid(), getppid);
break;
}
}
sleep(i);
if (i < 5) {
printf("I am child, will exit, pid=%d, ppid=%d", getpid(), getppid());
}
else {
printf("I am father, pid=%d, ppid=%d", getpid(), getppid());
}
return 0;
}
运行结果
进程共享
父子进程共享的内容
父子进程fork之后,有哪些异同?
相同点:全局变量、data、text、堆栈、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式
不同点:进程ID、fork返回值、父进程ID、进程运行时间
那么是子进程是复制了一份父进程0-3G用户空间的内容,以及父进程的PCB,然后在映射到物理内存吗?当然不是
父子进程遵循读时共享写时复制的原则。
父子进程不共享的内容
全局变量,读共享,写复制的例子。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int var=00;
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("var=%d, child, pid=%d, ppid=%d", var, getpid(), getppid());
var = 1001;
printf("var=%d, child, pid=%d, ppid=%d", var, getpid(), getppid());
sleep(3);
printf("var=%d, child, pid=%d, ppid=%d", var, getpid(), getppid());
}
else if (pid > 0) {
sleep(1);//保证子进程可以修改var的值
printf("var=%d, father, pid=%d, ppid=%d", var, getpid(), getppid());
var = 2000;
printf("var=%d, father, pid=%d, ppid=%d", var, getpid(), getppid());
}
return 0;
}
运行结果
验证子进程是否共享文件描述符表,子进程负责写入数据,父进程负责读取数据
父子进程共享文件描述符。一个写,另一个能读。共享的。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.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) {
write(fd, "hello\n", 6);
}
else if (pid > 0) {
sleep(1);
write(fd, "world\n", 6);
wait(NULL);
}
return 0;
}
运行结果
exec函数族——用于执行其他程序
重点掌握execl和execlp
一旦开始执行就再也不回来了,除非出错了
测试一下ececlp函数
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int var=00;
int main(){
//execlp(const char *_file, const char *_arg, ...)
execlp("ls", "ls", "-l". NULL);
//不需要判断返回值
perror("exec err");
return 0;
}
运行结果(发现和ls -l还是有一点不一样)
把传入的参数改一下
execlp("ls", "ls", "-l", "--clolr=auto". NULL);
这样就和ls -l 的命令一样了,不然我们的程序执行出来是黄色的。
测试一下ececl函数
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int var=00;
int main(){
//execlp(const char *_file, const char *_arg, ...)
execl("/bin/ls", "ls", "--color=auto", "-l". NULL);
//不需要判断返回值
perror("exec err");
printf("hello \n");
return 0;
}
执行结果
底层调用
孤儿进程和僵尸进程
孤儿进程——父进程死了,子进程还活着
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("I am child\n");
while (1) {
printf("I am child\n");
}
}
else if (pid > 0) {
printf("I am father\n");
sleep(1);
printf("I am father, I will die \n");
}
return 0;
}
父进程死掉之后,ctrl+c也不能杀死子进程,因为他变成了孤儿进程,不归现在的shell管了。只能kill掉了。
僵尸进程——子进程死了,父进程并没有回收他的PCB资源等
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("I am child\n");
sleep(1);
printf("I am child, I will die \n");
}
else if (pid > 0) {
printf("I am father\n");
while (1) {
printf("I am father, very happy\n");
}
}
return 0;
}
查看执行结果,会发现子进程的标志位Z,zombie,僵尸
如何回收僵尸
wait
作用:
1、阻塞等待子进程的死亡
2、回收进程资源
3、查看死亡原因
pid_t wait(int *stasus);//status传出参数
返回值:
失败返回 -1,成功返回进程ID
测试wait函数
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("I am child,, I iwil die!");
sleep(1);
}
else if (pid > 0) {
printf("I am father, wait for child die!\n");
pid_t wpid = wait(NULL);
printf("wait ok, wpid=%d, pid=%d\n", wpid, pid);
while (1) {
sleep(1);
}
}
return 0;
}
运行结果
查看死亡原因
1、正常死亡 WIFEXITED
如果WIFEXITED为真,使用WEXITSTATUS得到退出状态
2、非正常死亡 WIFSIGNALED
如果WIFSIGNALED为真,使用WTERMSIG得到信号。
正常死亡的例子
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("I am child,, I iwil die!");
sleep(1);
return 101;
}
else if (pid > 0) {
printf("I am father, wait for child die!\n");
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;
}
运行结果
把上面的return 101; 改成 exir(102); 都是可以的。
非正常死亡的例子
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("I am child,, I iwil die!");
while (1) {
printf("I am chile, guo lai da wo!\n");
sleep(1);
}
return 101;
}
else if (pid > 0) {
printf("I am father, wait for child die!\n");
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;
}
运行结果
waitpid
对比wait和waitpid
不设置参数,waitpid就是非阻塞,如果把第三个参数不设置为0的话,要写个类似自旋锁的东西。waitpid的测试代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if (pid == 0) {
printf("I am child,, I iwil die!");
sleep(2);
}
else if (pid > 0) {
printf("I am father, pid=%d\n", getpid());
int ret;
//waitpid(-1, NULL, WHOHANG)的返回值大于0才代表回收成功
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);
}
}
return 0;
}
运行结果
wait回收多个子进程
生成五个子进程,然后回收他们。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
int n = 5;
int i = 0;
pid_t pid;
for (int 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 (int i = 0; i < 5; i++) {
pid_t wpid=wait(NULL);
printf("wpid=%d\n", wpid);
}
while (1) sleep(1);
}
return 0;
}
运行结果
waitpid回收多个子进程
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
int n = 5;
int i = 0;
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) {
printf("I am child , pid=%d\n", getpid());
break;
}
}
//此时是父进程
if (i == 5) {
printf("I am parent!\n");
//-1代表子进程全都死掉了
while (1) {
pid_t wpid = waitpid(-1, NULL, WNOHANG);
//回收工作完成,所有子进程都被回收了
if (wpid == -1) break;
//说明成功回收到了一个子进程
else if (wpid > 0) {
printf("wpid=%d\n", wpid);
}
}
while (1) sleep(1);
}
if (i < 5) {
printf("I am a child, i=%d, pid=%d\n", i, getpid());
}
return 0;
}
2019.4.7补充
——表示进程在休眠
——表示进程的优先度高
——表示优先度比较低
——控制台进程
——运行在前端的进程