Linux进程控制 2019.1.6(进程控制,单道程序和多道程序,进程的状态转化,MMU的作用,映射关系,进程控制块PCB,环境变量,fork函数,kill的各种信息,exec函数族)

进程相关的概念

程序——编译好的二进制文件

进程——运行着的程序

站在程序员的角度——运行一系列指令的过程

站在操作系统的角度——分配系统资源的基本单位

区别

  1. 程序占用磁盘,不占用系统资源
  2. 内存占用系统资源
  3. 一个程序对应多个进程,一个进程对应一个程序
  4. 程序没有生命周期,进程有生命周期

 

 

单道程序和多道程序

多道——宏观上并行,微观上串行。

 

 

 

进程的状态转化

 

 

 

 

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函数族——用于执行其他程序

重点掌握execlexeclp

一旦开始执行就再也不回来了,除非出错了

 

测试一下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补充

——表示进程在休眠

——表示进程的优先度高

——表示优先度比较低

——控制台进程

——运行在前端的进程

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值