Linux进程操作

Posted mChenys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)
	
		//

以上是关于Linux进程操作的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程操作

Linux进程操作

Linux进程操作

linux怎么查询nohup的进程

中秋节爆肝万字,带你熟悉Linux进程的基本操作!!!

中秋节爆肝万字,带你熟悉Linux进程的基本操作!!!