Linux系统学习(四)——进程复制与替换

目录

Linux 进程复制与替换

1、printf 函数输出问题

2、主函数参数介绍

 3、复制进程 fork

3.1 fork 方法

3.2 fork 练习

4、僵死进程及处理方法

5、进程替换

5.1 什么是进程替换

5.2 进程替换注意事项

5.3替换函数(unistd.h)

execl

execlp

execv

execle

5.4 进程替换原理

5.5 进程替换实例


Linux 进程复制与替换

1、printf 函数输出问题

printf 函数并不会直接将数据输出到屏幕,而是先放到缓冲区中,只有一下三种情况满足,才会输出到屏幕。

1) 缓冲区满
2) 强制刷新缓冲区 fflush
3) 程序结束时
有缓冲区的优势:降低内核消耗,不用重复执行打印操作,等需要打印的数据在缓冲区放满之后,进行一次打印即可。如果没有放满,到程序结束时,先刷缓冲区,也可打印
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、主函数参数介绍

int main( int argc, char* argv[], char* envp[])
(1) argc 参数个数
(2) argv 参数内容
(3) envp 环境变量
先创建一个“test1126.c ” 的文件,文件内容如图所示
#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 方法

pid_t fork(void);
函数返回类型 pid_t 实质是 int 类型,Linux 内核 2.4.0 版本的定义是:

 

fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程。
在父进程中返回子进程的 pid,在子进程中返回 0,失败返回-1。
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. }
要注意的几个问题:
1) 父子进程并发运行的理解
2) 逻辑地址 物理地址
3) 写时拷贝技术

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) 僵死进程概念:子进程先于父进程结束,父进程没有调用 wait 获取子进程退出码。
(2) 如何处理僵死进程:父进程通过调用 wait()完成。
(3) Init 进程收养孤儿进程
代码示例:
先给出一个能产生僵死进程的代码,如下:
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. }
运行结果如下图:

 从上图中可以看到,当子进程结束后,并没有消失,仍然可以在系统中观测到,但此时

子进程其实已经运行结束了,此时子进程的状态被称为僵死状态,系统把处于该类状态的进
程称为僵死进程<defunct>。 如果父进程先结束,子进程最后是不会变为僵死进程的。
如下代码,处理了僵死进程,子进程结束后,会消失:
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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值