Linux进程操作

一、进程和程序相关概念

程序:编译好的二进制文件
进程:运行中的程序,对程序员而言就是运行一系列指令的过程.对操作系统而言就是分配系统资源的基本单位.

区别:
程序占用磁盘,不占用系统资源
进程占用系统资源
一个程序对应多个进程,一个进程对应一个程序.
程序没有生命周期,而进程有.

1.1 进程的状态转化

在这里插入图片描述
MMU的作用
内存管理单元,作用就是将虚拟的内存地址和物理内存进行绑定.
在这里插入图片描述

二、环境变量

环境变量实质在操作系统中用来指定运行环境的一些参数,通常具备如下特征
a.字符串本质
b.有统一格式:名=值[:值]
c.用来描述进程环境信息
存储形式:与命令行参数类似,char* environ[],内部存储字符串.

在linux系统上使用env命令可以查看所有的环境变量. 如果想查看某个环境变量可以使用echo命令,例如 echo $PATH 查看path的环境变量信息.

使用形式:与命令行参数类似
加载位置:与命令行参数类似
引入环境变量表:必须声明环境变量 extern char **environ

2.1 getenv/setenv/unsetenv函数

getenv用于获取环境变量
在这里插入图片描述
setenv用于设置环境变量,unsetenv用于取消环境变量
在这里插入图片描述
其中,setenv的overwrite参数为1表示覆盖原环境变量,为0表示不覆盖

如何使用?
以打印home的环境变量为例

#include<stdio.h>
#include<stdlib.h>

int main()
{
	char* env = getenv("HOME");
	printf("home=%s\n",env);
	return 0;
}

执行结果如下:
在这里插入图片描述

三、创建进程

3.1 fork函数

用于创建新的进程
在这里插入图片描述
返回值
失败:-1
成功,两次返回,父进程会返回子进程的id,子进程会返回 0

图示:
在这里插入图片描述
如何使用

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
	printf("Begin....\n");
	pid_t id = fork(); //fork进程,会多出一个子进程,从这句话开始,后面的逻辑会执行2次,分别是当前进程以及它的子进程执行
	printf("End....\n");
	return 0;
}

编译执行
在这里插入图片描述
从上图结果中可以看出fork之后确实分支出了一个进程, 因为打印了两次End…

3.2 getpid/getppid函数

getpid用于获取当前进程的id、getppid用于获取父进程的id
在这里插入图片描述
如何使用

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
	printf("Begin...\n");

	pid_t pid = fork();

	if(pid <0)
	{
		perror("fork fail");
		return -1;
	}

	if(pid == 0)
	{
		//进来的是子进程
		printf("子进程:selfid=%d,ppid=%d\n",getpid(),getppid());
	}else if(pid > 0 )
	{
		//进来的是父进程,返回值pid得到的是子进程的id
		printf("父进程:selfid=%d,chidid=%d,ppid=%d\n",getpid(),pid,getppid());
		//睡1秒,避免父进程先退出,导致子进程获取ppid失败
		sleep(1);
	}
	printf("End...\n");
	return 0;
}

编译执行
在这里插入图片描述
从结果中可以看到子进程的getppid等于父进程的getpid

3.3 创建n个子进程

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{

	for(int i=0;i<5;++i)
	{
		pid_t pid = fork();
		if(pid == 0)
		{
			//子进程
			printf("子进程:self=%d,ppid=%d\n",getpid(),getppid());
			break;//子进程需要跳出,不用继续参与循环,否则会子生子
		}else if(pid >0)
		{
			//父进程
			printf("父进程:self=%d,childpid=%d\n",getpid(),pid);
		}
	}
 //避免程序退出
	while(1)
	{
		sleep(1);
	}
	return 0;
}

编译执行
在这里插入图片描述
通过命令查看有多少个./fork2进程 ,有6个,其中一个是父进程23501,其他都是子进程
在这里插入图片描述

3.4 循环创建子进程并控制顺序

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
	int i;
	for(i = 0 ; i < 5 ; ++ i )
	{
		pid_t pid = fork();
		if(pid ==0 )
		{
			printf("创建子进程%d,self=%d,ppid=%d\n",(i+1),getpid(),getppid());
			break;
		}
	}
	sleep(i);

	if(i < 5)
	{
		printf("子进程%d退出,self=%d,ppid=%d\n",(i+1),getpid(),getppid());
	}else
	{
		printf("父进程退出,self=%d\n",getpid());
	}
	return 0;
}

编译执行
在这里插入图片描述

四、进程的控制命令

Linux上操作进程的几个命令:

  • ps aux :可以查看所有的进程id和名字等信息

  • ps ajx可以查看进程组的相关信息, 其中init进程是所有进程的祖先,它的pid=1, ppid(父进程)=0表示没有父进程.
    在这里插入图片描述

  • kill: 用于杀进程 ,-l选项可以查看其他选项
    在这里插入图片描述
    例如 kill -9 pid :表示通过信号杀死某进程.

  • ps:通常结合|管道和grep命令来结合使用
    例如:ps aux|grep a.out|grep -v grep |wc -l表示用于统计a.out进程的个数,其中wc-l是统计行数,由于前面都是a.out的进程信息,那其实就是统计个数了. grep -v grep表示排除带有grep的进程,避免grep命令的影响

五、父子进程共享内容的操作原则

父子进程相同之处有:全局变量、data、text、栈、堆、环境变量、用户id、宿主目录、进程工作目录、信号处理方式等。

不同之处有:进程id、fork返回值、父进程id、进程运行实际、闹钟(定时器)、未解决信号集

父子进程遵循读时共享,写时复制的原则,这样可以节省内存开销。

例如:

#include <stdio.h>
#include <unistd.h>

int val = 100;

int main()
{
	pid_t pid = fork(); // 后面的代码开始分别在主进程和子进程运行
	if (pid == 0)
	{
		//子进程
		printf("chid val1=%d\n", val); // 100
		val = 200;
		printf("child val2=%d\n", val); // 200
		sleep(3);
		printf("child val3=%d\n", val); // 200
	}
	else if (pid > 0)
	{
		//父进程
		printf("parent val1=%d\n", val); // 100
		sleep(1);
		val = 1000;
		printf("parent val2=%d\n", val); // 1000
	}
	return 0;
}

编译执行,从结果可以看出,无论是子进程还是父进程修改变量的时候都是修改自己副本(复制出来的)那份数据.彼此间并不会受到干扰.
在这里插入图片描述

六、exec族函数介绍

exec族函数共有6种函数,统称exec函数.常用的是execl和execlp
在这里插入图片描述
用于执行其他程序,类似Windows的system命令.

注意:调用exec族函数时,并不会创建新的进程,也就是说执行exec函数前后该进程的id不会发生改变,exec族函数只是将当前进程的.text和.data替换为要加载程序的.text和.data,然后让进程从新的.text第一条指令开始执行,当前进程继续保持原有的id.

6.1 execl函数

执行程序的时候,可以指定环境变量的位置,执行的程序可以不用加路径
参数

  • path:环境变量
  • arg:参数列表, 参数列表需要使用一个NULL作为结尾

返回值
只有失败才有返回值

#include<stdio.h>
#include<unistd.h>

int main()
{
	execl("/bin/ls","ls","-l","--color=auto",NULL);
	perror("exec err");
	return 0 ;
}

6.2 execlp

execlp会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。
参数

  • file: 要执行的程序
  • arg 参数列表, 参数列表需要使用一个NULL作为结尾

返回值
只有失败才有返回值

#include<stdio.h>
#include<unistd.h>

int main()
{
	execlp("ls","ls","-l","--color=auto",NULL);
	perror("exec err");
	return 0 ;
}

编译执行
在这里插入图片描述

七、孤儿进程和僵尸进程

7.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\n", getpid());
		sleep(2);
		printf("Iam parent ,I will die!\n");
	}

	return 0;
}

编译执行
在这里插入图片描述
可以看到父进程死掉后, 子进程任然存在,并且它的父进程id也改变了, 此时如果想杀掉子进程,那么只能通过kill -9 4940 其中4940就是上面子进程的id

7.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(1);
		printf("Iam child,I die\n");
	}
	else if (pid > 0)
	{
		while (1)
		{
			printf("I am father,pid=%d\n", getpid());
			sleep(1);
		}
	}
	return 0;
}

编译执行
在这里插入图片描述
通过ps命令查看僵尸进程,进程名字会用[]括起来,同时前面会有一个Z+标记.
在这里插入图片描述
僵尸进程应该避免出现,会占用系统资源,僵尸进程是无法通过kill命令杀死的,应为它本来就死了,想把僵尸进程回收了,那么就需要把父进程也杀了.
在这里插入图片描述

八、进程回收

进程回收会用到wait或者waitpid函数
在这里插入图片描述

8.1 wait函数

它是一个阻塞的函数,用于回收子进程的资源,它会通过传入参数通知外部子进程的死亡原因
参数

  • wstatus:传出参数,通过这个值调用相应的函数可以获取子进程的死亡原因

返回值

  • 成功返回终止的子进程id,失败返回-1

子进程的死亡原因

  • 正常死亡:WIFEXITED(id)
    如果WIFEXITED返回真,使用WEXITSTATUS得到退出状态

  • 非正常死亡:WIFSIGNALED(id)
    如果WIFSIGNALED返回真,使用WTERMSIG得到信号

这些都可以在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,pid=%d,ppid=%d\n", getpid(), getppid());
		sleep(1);
		printf("I am child,I die!\n");
		return 101; //或者用exit(101); 这样正常死亡,通过wait可以得到返回值.
	}
	else if (pid > 0)
	{
		printf("I am parent,pid=%d\n", getpid());

		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))
		{
			//如果子进程是通过kill 信号杀死的,那么这里会得到信号的值
			printf("child killed by %d\n", WTERMSIG(status));
		}

		//保持父进程存活
		while (1)
		{
			sleep(1);
		}
	}
	return 0;
}

编译执行
在这里插入图片描述
此时再通过ps查看也看不到僵尸进程了,只留下父进程了
在这里插入图片描述

8.2 waitpid

pid_t waitpid(pid_t pid, int *wstatus, int options);

参数

  • pid:

pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

返回值

  • 如果设置了WNOHANG,那么如果没有子进程退出,返回0,如果有子进程退出返回退出的pid
    失败返回-1
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("I am child, pid=%d,ppid=%d\n",getpid(),getppid());
		sleep(2);
	}else if(pid > 0 )
	{
		printf("I am fater id=%d\n",getpid());
		int ret = 0;
		while((ret = waitpid(-1,NULL,WNOHANG)) == 0)
		{
			//参数1为-1表示可以等等任何进程的退出
			//参数2为NULL,表示不需要接收错误码
			//参数3为WNOHANG表示不阻塞
			//返回值,如果参数3为WNOHANG,那么如果返回值为0表示没有子进程退出
			sleep(1);
		}
		//来到这说明有子进程退出了
		printf("退出的子进程pid=%d\n",ret);

		//尝试继续判断是否退出,由于上面已经捕获到了退出子进程,下面再捕获也没得捕获了
		ret = waitpid(-1,NULL,WNOHANG);
		if(ret < 0)
		{
			//失败返回-1
			perror("wait err");
		}

		//保持父进程存活
		while(1)
		{
			sleep(1);
		}
	}
	return 0;
}

编译执行
在这里插入图片描述

8.3 用wait回收多个子进程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	int i;
	for (i = 0; i < 5; ++i)
	{
		pid_t pid = fork();
		if (pid == 0)
		{
			// 打印子进程的pid
			printf("create child %d,pid=%d\n", (i + 1), getpid());
			break;
		}
	}

	printf("i=%d\n", i);
	//睡个i秒,让子进程按顺序退出
	sleep(i);

	if (i == 5)
	{
		//父进程的逻辑,回收退出的子进程
		for (int i = 0; i < 5; ++i)
		{
			pid_t wpid = wait(NULL);
			printf("回收child ,pid=%d\n", wpid);
		}
	}

	return 0;
}

编译执行
在这里插入图片描述

8.4 用waitpid回收多个子进程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	int i;
	for (i = 0; i < 5; ++i)
	{
		pid_t pid = fork();
		if (pid == 0)
		{
			break; // 父进程退出
		}
	}

	if (i == 5)
	{
		//父进程
		printf("I am parent pid=%d\n", getpid());
		while (1)
		{
			pid_t wpid = waitpid(-1, NULL, WNOHANG);
			if (wpid == -1)
			{
				//不存在退出的子进程了,说明全部回收完了
				break;
			}
			if (wpid > 0)
			{
				printf("回收子进程pid=%d\n", wpid);
			}
		}

		//保证父进程存在
		while (1)
		{
			sleep(1);
		}
	}

	if (i < 5)
	{
		//		sleep(i);
		printf("退出的子进程i=%d,pid=%d\n", i, getpid());
	}
	return 0;
}

编译执行
在这里插入图片描述

九、父子进程共享文件描述符的状态

父子进程在使用同一个文件描述符写数据的时候,谁先写都会影响后者对方写入的位置,如下代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char *args[])
{
	if (argc != 2)
	{
		printf("Usage:./sharefd filename\n");
		return -1;
	}

	int fd = open(args[1], O_RDWR | O_CREAT, 0666);
	if (fd < 0)
	{
		perror("open fail\n");
	}
	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);
	}
	close(fd);
	return 0;
}

编译执行
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值