目录
Linux 进程复制与替换
1、printf 函数输出问题
printf 函数并不会直接将数据输出到屏幕,而是先放到缓冲区中,只有一下三种情况满足,才会输出到屏幕。
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. int main(int argc, char* argv[],char* envp[])
5. {
6. printf("hello");
7. //fflush(stdout);
8. sleep(3);
9. exit(0);
10. }
2、主函数参数介绍
#include <string.h>
5. #include <assert.h>
6.
7. //参数个数 参数内容 环境变量
8. int main(int argc, char* argv[],char* envp[])
9. {
10. int i = 0;
11. printf("argc=%d\n",argc);
12.
13. for( ;i < argc; i++ )
14. {
15. printf("argv[%d]=%s\n",i,argv[i]);
16. }
17.
18. for( i = 0; envp[i] != NULL; i++ )
19. {
20. printf("envp[%d]=%s\n",i,envp[i]);
21. }
22. exit(0);
23. }
代码执行结果:
3、复制进程 fork
fork函数
功能:创建一个与原来进程几乎完全相同的进程
这也就代表着,父进程可通过调用该函数创建一个子进程,两个进程可以做完全相同的事
返回值:pid_t类型的变量,也就是进程id类型的变量
3.1 fork 方法
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <string.h>
5. #include <assert.h>
6.
7. int main(int argc, char* argv[],char* envp[])
8. {
9. char * s = NULL;
10. int n = 0;
11.
12. pid_t pid = fork();
13. assert( pid != -1 );
14.
15. if ( pid == 0 )
16. {
17. s = "child";
18. n = 4;
19. }
20. else
21. {
22. s = "parent";
23. n = 7;
24. }
25.
26. int i = 0;
27.
28. for(; i < n; i++ )
29. {
30. printf("pid=%d,n=%d,&n=%x,s=%s\n",getpid(),n,&n,s);
31. sleep(1);
32. }
33.
34. exit(0);
35. }
3.2 fork 练习
下列程序输出几个“A”?
int main(int argc, char* argv[],char* envp[])
2. {
3. int i = 0;
4. for( ; i < 2; i++ )
5. {
6. fork();
7. printf("A\n");
8. }
9. exit(0);
10. }

4、僵死进程及处理方法
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <string.h>
5. #include <assert.h>
6.
7. int main(int argc, char* argv[],char* envp[])
8. {
9. char * s = NULL;
10. int n = 0;
11.
12. pid_t pid = fork();
13. assert( pid != -1 );
14.
15. if ( pid == 0 )
16. {
17. s = "child";
18. n = 4;
19. }
20. else
21. {
22. s = "parent";
23. n = 10;
24. }
25.
26. int i = 0;
27.
28. for(; i < n; i++ )
29. {
30. printf("pid=%d,s=%s\n",getpid(),s);
31. sleep(1);
32. }
33.
34. exit(0);
35. }


从上图中可以看到,当子进程结束后,并没有消失,仍然可以在系统中观测到,但此时
8. int main()
9. {
10. char * s = NULL;
11. int n = 0;
12.
13. pid_t pid = fork();
14. assert( pid != -1 );
15.
16. if ( pid == 0 )
17. {
18. n = 4;
19. s = "child";
20. }
21. else
22. {
23. n = 10;
24. s = "parent";
25.
26. int val = 0;
27. int id = wait(&val);
28.
29. if ( WIFEXITED(val) )
30. {
31. printf("id=%d,val=%d\n",id,WEXITSTATUS(val));
32. }
33
34. }
35.
36. int i = 0;
37. for( ; i < n; i++ )
38. {
39. printf("pid=%d,s=%s\n",getpid(),s);
40. sleep(1);
41. }
42.
43. exit(3);
44. }

从上图可以看到子进程结束后,彻底从系统中消失了,并没有变成僵死进程。
5、进程替换
5.1 什么是进程替换
当我们fork()生成子进程后,子进程的代码与数据可以来自其他可执行程序。把磁盘上其他程序的数据以覆盖的形式给子进程。这样子进程就可以执行全新的程序了,这种现象称为程序替换。

5.2 进程替换注意事项
1.进程替换不会创建新进程,因为进程替换只是将该进程的数据替换为指定的可执行程序。而进程PCB没有改变,所以不是新的进程,进程替换后不会发生进程pid改变。
2.进程替换后,如果替换成功后则替换函数下的代码不会执行,因为进程替换是覆盖式的替换,替换成功后进程原来的代码就消失了。同理在进程替换失败后会执行替换函数后的代码
3.进程替换函数在进程替换成功后不返回,函数的返回值表示替换失败
4.进程替换成功后,退出码为替换后的进程的退出码
5.3替换函数(unistd.h)
execl
函数原型
int execl(const char *path,const char *arg,…)
参数解释
path为可执行程序的路径,arg为如何执行这个可执行程序
…为可变参数,指的是给这执行程序携带的参数,在参数末尾加NULL表示参数结束
返回值:替换失败返回-1,替换成功不返回
execlp
函数原型:
int execlp(const char *file, const char *arg,…)
参数解释:
file:要替换的目标程序,arg:如何执行这个程序,…为给这个程序传的参数。最后以NULL结束表示传参结束
返回值:进程替换失败后返回-1。
相比于execl :execp默认在Linux环境变量PATH中查找可执行程序
execv
函数原型:
int execv(const char* path,char* const argv[ ]);
参数解释:
argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束,给execv
path:替换目标程序路径,
返回值:进程替换失败返回-1
execle
函数原型:
int execle(const char* path, const char* arg, …,char* const envp[ ])
参数解释:
path为替换的目标程序路径,arg与…表示如何执行替换后的程序以NULL结尾。
envp数组为要导入的环境变量
返回值:
替换失败返回-1
5.4 进程替换原理
进程替换不会创建新的进程,进程PCB未发生改变,进程实体(数据代码内容)被替换:进程调用上述一种 exec 函数时,该进程完全由新程序替换,而新程序则从其 main 函数开始执行。因为调用 exec 并不创建新进程,所以前后的进程 ID (当然还有父进程号、进程组号、当前工作目录……)并未改变。exec 只是用另一个新程序替换了当前进程的正文、数据、堆和栈段(进程替换)。
5.5 进程替换实例
// main程序,main程序中再进行进程替换成ps -f程序
#include <stdio.h>
#include <string.h>
#include <unistd.h>
// ls -l /home
int main ()
{
printf("hello itcast\n");
// arg0 arg1 arg2 .... argn
// arg0一般是可执行文件名, argn必须是NULL
//execlp("ls", "ls", "-l", "/home", NULL);
// 第一个参数是可执行文件的相对路径或者绝对路径
// 第二个参数是可执行文件的名字
// 中间的参数是可执行文件的参数
// 最后一个参数必须是NULL
//execl("/bin/ls", "ls", "-l", "/home", NULL);
// 第一个参数是可执行文件的名字
// 第二个参数是指针数组,最后一定以NULL结束
// char *argv[] = {"ls", "ls", "-l", "/home", NULL};
// execvp("ls", argv);
// 最后一个参数是环境变量指针数组,最后一定以NULL结束
char *envp[] = {"ADDR=BEIJING", NULL};
execle("ls", "ls", "-l", "/home", NULL, envp);
printf("hello world\n"); // 注意:如果进程替换执行成功,本行不会被执行
return 0;
}
当然我们也可以用 fork 创建子进程后,主进程继续执行原有程序,子进程调用一种 exec 函数以执行另一个程序。
替换的过程
- 获取命令行
- 解析命令行
- 建立一个子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程退出(wait)