目录
一、Linux中的进程状态
- R 运行状态: 表明进程是在运行中或者在运行队列里。
- S 睡眠状态: 意味着进程在等待事件完成。
- D 磁盘休眠状态:让进程在磁盘写入完毕期间,这个进程不能被任何人杀掉。
- T 停止状态: 可以通过发送 SIGSTOP(kill -19) 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号(kill -18)让进程继续运行。
- X 死亡状态:操作系统将该进程的数据全部释放掉。
- Z 僵尸进程:进程一般退出的时候,如果父进程,没有主动回收子进程信息,子进程会一直让自己出于Z状态,进程的相关资源尤其是task_struct结构体不能被释放。
二、Linux中查看进程状态有关的指令
1.1 ps ajx/ps aux
ps ajx:显示作业控制信息
ps aux:显示所有用户的所有进程信息
| head -1 :表示提取第一行信息
1.2 grep
grep 命令用于获取指定的进程信息,加上一个循环和sleep,让它1s输出一行。
这里添加 grep -v grep 的意思是从某个文本或命令的输出中排除包含 "grep" 关键词的行。通常在使用 ps 命令等查看进程列表时,为了排除自身 grep 命令所产生的行,可以使用 grep -v grep
1.3 ls/proc
用于显示系统中动态运行的所有进程的信息:
三、进程等待
3.1 进程等待解释
当我们用fork创建了子进程,子进程帮父进程完成某种任务后,父进程需要用 wait或者waitpid等待子进程的退出。
3.2 进程等待的原因
- 通过获取子进程退出的消息,父进程可以得知子进程的执行结果。
- 进程等待可以保证子进程先退出,父进程后退出。
- 子进程退出的时候会先进入僵尸状态,进程一旦变成僵尸状态,连kill -9也没办法杀死,因为没办法杀死一个已经死去的进程。这时候会有内存泄漏的问题,就需要父进程等待,来释放子进程占用的资源。
3.3 进程等待方法
我们使用man 2 wait来查看:
可以看到它包含了三个方法:wait,waitpid,waitid,且头文件为:<sys/types.h>和<sys/wait.h>
我们今天来了解前两种
3.3.1 wait
返回值:
成功时返回等待进程的pid,失败返回-1。
参数:
输出型参数,获取子进程的退出状态,不关心则设置成为NULL
测试:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
// child
int cnt = 5;
while(cnt)
{
printf("Child is running: cnt=%d\n", cnt);
--cnt;
sleep(1);
}
exit(0);
}
sleep(10);
printf("father wait begin\n");
// father
pid_t ret = wait(NULL);
if(ret > 0)
{
printf("father wait: %d, success\n", ret);
}
else{
printf("father wait failed\n");
}
sleep(10);
return 0;
}
我们让子进程运行五次后就退出,让父进程等待回收:
while :; do ps ajx | head -1 && ps ajx | grep myfork|grep -v grep;sleep 1;echo "----------------------------";done
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 Z+ 1001 0:00 [myfork] <defunct>
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 Z+ 1001 0:00 [myfork] <defunct>
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 Z+ 1001 0:00 [myfork] <defunct>
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
5175 5176 5175 1116 pts/0 5175 Z+ 1001 0:00 [myfork] <defunct>
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
----------------------------
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1116 5175 5175 1116 pts/0 5175 S+ 1001 0:00 ./myfork
可以看到子进程的状态从睡眠状态,变成僵尸进程,最后被父进程回收。
3.3.2 waitpid
返回值:
- 正常返回:waitpid返回收集到的子进程的进程pid
- 如果options设置成
WNOHANG
选项,表示非阻塞等待,调用时waitpid发现自己没有退出的子进程可以收集,直接退出返回0 - 如果调用出错,则返回-1,这时errno会被设置成相应的值,指示错误所在。
参数:
pid :
pid=-1时,表示任一子进程,等同于wait
pid>0,等待id和pid相等的子进程。
status :
WIFEXITED(status):查看进程是否正常退出,正常退出-真(with if exited)
WEXITSTATUS(status):查看进程的退出码,WIFEXITED非零,提取子进程的退出码。(with exit status)
options :
WNOHANG:如果pid的子进程没有结束,那么waitpid返回值是0,不予以等待,如果正常结束,返回该子进程的id。
0:表示阻塞等待
四、进程替换
4.1 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
4.2 替换函数
使用man查看手册:
可以看到有六种exec函数,它们的头文件为 <unistd.h>
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回;
- 如果调用出错则返回-1。
- 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解:
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list):表示参数采用列表;
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH;
- e(env):表示自己维护环境变量;
4.3 exec函数详解
4.3.1 execl
int execl(const char *path, const char *arg, ...);
说明:
- path:程序所在的路径。
- arg:替换的程序名。
- . . .:可变参数列表,其结尾必须以NULL结尾,这个我们在使用printf和scanf的时候见过。
- execl函数替换失败返回-1;
代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("进程pid:%d\n", getpid());
execl("/usr/bin/ls", "ls", NULL); //不带参数
//execl("/usr/bin/ls", "ls", "-al", NULL); //带参数
printf("进程替换成功\n");
return 0;
}
从运行结果可以看到,进程成功的被替换了,因此后面的printf语句直接没有执行。
4.3.2 execlp
int execlp(const char *file, const char *arg, ...);
说明:
这个函数与上一个execl很像,只是多了一个p,表示可以不带路径,只需要说明目标程序名;系统会通过环境变量PATH
查找。
代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("进程pid:%d\n", getpid());
execlp("ls", "ls","-al", NULL); //带参数
printf("进程替换成功\n");
return 0;
}
4.3.3 execv
int execv(const char *path, char *const argv[]);
说明:
path
:目标程序的路径;argv
:数组用来传参;- 替换失败返回-1,其中
v
表示数组(vector)。
代码:
int main()
{
printf("process is running.....\n");
char *const arr[]={"ls","-a","-l",NULL};
execv("/usr/bin/ls",arr);
return 0;
}
4.3.4 execle
int execle(const char *path, const char *arg, ...,char *const envp[]);
说明:
path
:目标程序所在路径;arg
:参数,传目标程序名;- ...:可变参数列表;
envp
:用户传给目标程序的环境变量;- 失败返回-1。
代码:
我们使用自己写的mytest的程序替换myexec程序:
mytest:
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
cout << "PATH: " << getenv("PATH") << endl;
//cout << "MYPATH: " << getenv("MYPATH") << endl;
cout << "hello world1" << endl;
cout << "hello world2" << endl;
cout << "hello world3" << endl;
cout << "hello world4" << endl;
cout << "hello world5" << endl;
return 0;
}
myexec:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
printf("进程pid:%d\n", getpid());
pid_t id = fork();
if(id == 0)
{
//child
printf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());
char *const env_[] = {
(char*)"MYPATH=HELLOWORLD",
NULL
};
execle("./mytest", "mytest", NULL, env_);
exit(1);
}
//parent
printf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());
pid_t ret = waitpid(id, NULL, 0);
if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);
return 0;
}
运行结果:
可以看到子进程替换后父进程也是可以正常运行的,这是因为进程是独立的,但是PATH并没有输出内容,说明此时进程中找不到PATH这个环境变量
如果输出MYPATH,可以看到程序正常运行:
因此可以得出结论:带e
的exec函数会添加环境变量给目标进程,执行的是覆盖式的。
使用execl函数PATH环境变量可以正常打印。
如果想要将系统的环境变量保存并且也可以使用用户自己的环境变量,可以使用以下两种方法:
1. export指令添加环境变量:
export MYPATH="HELLO WORLD"
2. 先使用putenv函数将自定义环境变量加载进系统环境变量里面去,然后再传environ指针:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
extern char** environ;
printf("process is running.....\n");
putenv("MYENV=11223344");
execle("./mybin","mybin",NULL,environ);
return 0;
}
4.3.5 execve
int execve(const char *path, char *const argv[], char *const envp[]);
与execle相似,不过传的是数组 。
4.3.6 execvp
int execvp(const char *file, char *const argv[]);
与execve相似,不过传的是系统的环境变量。