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进程操作的主要内容,如果未能解决你的问题,请参考以下文章