有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些方法及它们之间的区别。
 
一、system函数调用
system函数的原型为:
 
  1. #include <stdlib.h>
  2. int system (const char *string);
它的作用是,运行以字符串参数的形式传递给它的命令并等待该命令的完成。命令的执行情况就如同在shell中执行命令:sh -c string。如果无法启动shell来运行这个命令,system函数返回错误代码127;如果是其他错误,则返回-1。否则,system函数将返回该命令的退出码。
 
注意:system函数调用用一个shell来启动想要执行的程序,所以可以把这个程序放到后台中执行,这里system函数调用会立即返回。
 
可以先先下面的例子,源文件为new_ps_system.c,代码如下:
 
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main()
  4. {
  5. printf("Running ps with system\n");
  6. //ps进程结束后才返回,才能继续执行下面的代码
  7. system("ps au");// 1
  8. printf("ps Done\n");
  9. exit(0);
  10. }
该程序调用ps程序打印所有与本用户有关的进程,最后才打印ps Done。运行结果如下:
 
如果把注释1的语句改为:system("ps au &");则system函数立即返回,不用等待ps进程结束即可执行下面的代码。所以你看到的输出,ps Done可能并不是出现在最后一行,而是在中间。
 
一般来说,使用system函数不是启动其他进程的理想手段,因为它必须用一个shell来启动需要的程序,即在启动程序之前需要先启动一个shell,而且对shell的环境的依赖也很大,因此使用system函数的效率不高。
 
二、替换进程映像——使用exec系列函数
exec系列函数由一组相关的函数组成,它们在进程的启动方式和程序参数的表达方式上各有不同。但是exec系列函数都有一个共同的工作方式,就是把当前进程替换为一个新进程,也就是说你可以使用exec函数将程序的执行从一个程序切换到另一个程序,在新的程序启动后,原来的程序就不再执行了,新进程由path或file参数指定。exec函数比system函数更有效。
 
exec系列函数的类型为:
 
  1. #include <unistd.h>
  2. char **environ;
  3. int execl (const char *path, const char *arg0, ..., (char*)0);
  4. int execlp(const char *file, const char *arg0, ..., (char*)0);
  5. int execle(const char *path, const char *arg0, ..., (char*)0, char *const envp[]);
  6. int execv (const char *path, char *const argv[]);
  7. int execvp(cosnt char *file, char *const argv[]);
  8. int execve(const char *path, char *const argv[], char *const envp[]);
这类函数可以分为两大类,execl、execlp和execle的参数是可变的,以一个空指针结束,而execv、execvp和execve的第二个参数是一个字符串数组,在调用新进程时,argv作为新进程的main函数的参数。而envp可作为新进程的环境变量,传递给新的进程,从而变量它可用的环境变量。
 
承接上一个例子,如果想用exec系统函数来启动ps进程,则这6个不同的函数的调用语句为:
注:arg0为程序的名字,所以在这个例子中全为ps。
 
 
  1. char *const ps_envp[] = {"PATH=/bin:usr/bin", "TERM=console", 0};
  2. char *const ps_argv[] = {"ps", "au", 0};
  3. execl("/bin/ps", "ps", "au", 0);
  4. execlp("ps", "ps", "au", 0);
  5. execle("/bin/ps", "ps", "au", 0, ps_envp);
  6. execv("/bin/ps", ps_argv);
  7. execvp("ps", ps_argv);
  8. execve("/bin/ps", ps_argv, ps_envp);
下面我给出一个完整的例子,源文件名为new_ps_exec.c,代码如下:
 
  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. int main()
  5. {
  6. printf("Running ps with execlp\n");
  7. execlp("ps", "ps", "au", (char*)0);
  8. printf("ps Done");
  9. exit(0);
  10. }
运行结果如下:
细心的话,可以发现,最后的ps Done并没有输出,这是偶然吗?并不是,因为我们并没有再一次返回到程序new_ps_exec.exe上,因为调用execlp函数时,new_ps_exec.exe进程被替换为ps进程,当ps进程结束后,整个程序就结束了,并没有回到原来的new_ps_exec.exe进程上,原本的进程new_ps_exec.exe不会再执行,所以语句printf("ps Done");根本没有机会执行。
 
注意,一般情况下,exec函数是不会返回的,除非发生错误返回-1,由exec启动的新进程继承了原进程的许多特性,在原进程中已打开的文件描述符在新进程中仍将保持打开,但任何在原进程中已打开的目录流都将在新进程中被关闭。
 
三、复制进程映像——fork函数
1、fork函数的应用
exec调用用新的进程替换当前执行的进程,而我们也可以用fork来复制一个新的进程,新的进程几乎与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间、环境和文件描述符。
 
fork函数的原型为:
 
  1. #include <sys/type.h>
  2. #include <unistd.h>
  3. pid_t fork();
注:在父进程中,fork返回的是新的子进程的PID,子进程中的fork返回的是0,我们可以通过这一点来判断父进程和子进程,如果fork调用失败,它返回-1.
 
继承上面的例子,下面我给出一个调用ps的例子,源文件名为new_ps_fork.c,代码如下:
 
  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. int main()
  6. {
  7. pid_t pid = fork();
  8. switch(pid)
  9. {
  10. case -1:
  11. perror("fork failed");
  12. exit(1);
  13. break;
  14. case 0:
  15. //这是在子进程中,调用execlp切换为ps进程
  16. printf("\n");
  17. execlp("ps", "ps", "au", 0);
  18. break;
  19. default:
  20. //这是在父进程中,输出相关提示信息
  21. printf("Parent, ps Done\n");
  22. break;
  23. }
  24. exit(0);
  25. }
输出结果为:
 
我们可以看到,之前在第二点中没有出现的ps Done是打印出来了,但是顺序却有点不对,这是因为,父进程先于子程序执行,所以先输出了Parent, ps Done,那有没有办法让它在子进程输出完之后再输出,当然有,就是用wait和waitpid函数。注意,一般情况下,父进程与子进程的生命周期是没有关系的,即便父进程退出了,子进程仍然可以正常运行。
 
2、等待一个进程
wait函数和waitpid函数的原型为:
 
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t wait(int *stat_loc);
  4. pid_t waitpid(pid_t pid, int *stat_loc, int options);
wait用于在父进程中调用,让父进程暂停执行等待子进程的结束,返回子进程的PID,如果stat_loc不是空指针,状态信息将被写入stat_loc指向的位置。
 
waitpid等待进程id为pid的子进程的结束(pid为-1,将返回任一子进程的信息),stat_loc参数的作用与wait函数相同,options用于改变waitpid的行为,其中有一个很重要的选项WNOHANG,它的作用是防止waippid调用者的执行挂起。如果子进程没有结束或意外终止,它返回0,否则返回子进程的pid。
 
改变后的程序保存为源文件new_ps_fork2.c,代码如下:
 
  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. int main()
  6. {
  7. pid_t pid = fork();
  8. int stat = 0;
  9. switch(pid)
  10. {
  11. case -1:
  12. perror("fork failed");
  13. exit(1);
  14. break;
  15. case 0:
  16. //这是在子进程中,调用execlp切换为ps进程
  17. printf("\n");
  18. execlp("ps", "ps", "au", 0);
  19. break;
  20. default:
  21. //这是在父进程中,等待子进程结束并输出相关提示信息
  22. pid = wait(&stat);
  23. printf("Child has finished: PID = %d\n", pid);
  24. //检查子进程的退出状态
  25. if(WIFEXITED(stat))
  26. printf("Child exited with code %d\n", WEXITSTATUS(stat));
  27. else
  28. printf("Child terminated abnormally\n");
  29. printf("Parent, ps Done\n");
  30. break;
  31. }
  32. exit(0);
  33. }
输出为:
可以看到这次的输出终于正常了,Parent的输出也在子进程的输出之后。
 
总结——三种启动新进程方法的比较
首先是最简单的system函数,它需要启动新的shell并在新的shell是执行子进程,所以对环境的依赖较大,而且效率也不高。同时system函数要等待子进程的返回才能执行下面的语句。
 
exec系统函数是用新的进程来替换原先的进程,效率较高,但是它不会返回到原先的进程,也就是说在exec函数后面的所以代码都不会被执行,除非exec调用失败。然而exec启动的新进程继承了原进程的许多特性,在原进程中已打开的文件描述符在新进程中仍将保持打开,但需要注意,任何在原进程中已打开的目录流都将在新进程中被关闭。
 
fork则是用当前的进程来复制出一个新的进程,新进程与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间、环境变量和文件描述符,我们通常根据fork函数的返回值来确定当前的进程是子进程还是父进程,即它并不像exec那样并不返回,而是返回一个pid_t的值用于判断,我们还可以继续执行fork后面的代码。感觉用fork与exec系列函数就能创建很多需的进程

Linux启动新进程的几种方法及比较[转]的更多相关文章

  1. Linux启动新进程的几种方法汇总

    有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些 ...

  2. Linux启动新进程的几种方法及比较

    有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些 ...

  3. Linux启动新进程的三种方法

    程序中,我们有时需要启动一个新的进程,来完成其他的工作.下面介绍了三种实现方法,以及这三种方法之间的区别. 1.system函数-调用shell进程,开启新进程system函数,是通过启动shell进 ...

  4. Linux中Kill进程的N种方法

    常规篇: 首先,用ps查看进程,方法如下: $ ps -ef …… smx       1822     1  0 11:38 ?        00:00:49 gnome-terminal smx ...

  5. linux 下隐藏进程的一种方法

    前言 本文所用到的工具在 https://github.com/gianlucaborello/libprocesshider 可以下载 思路就是利用 LD_PRELOAD 来实现系统函数的劫持 LD ...

  6. Linux之Kill进程的N种方法

    常规篇: 首先,用ps查看进程,方法如下: $ ps -ef …… smx       1822     1  0 11:38 ?        00:00:49 gnome-terminal smx ...

  7. linux下查询进程占用的内存方法总结

    linux下查询进程占用的内存方法总结,假设现在有一个「php-cgi」的进程 ,进程id为「25282」.现在想要查询该进程占用的内存大小.linux命令行下有很多的工具进行查看,现总结常见的几种方 ...

  8. python实现Linux启动守护进程

    python实现Linux启动守护进程 DaemonClass.py代码: #/usr/bin/env python # -*- coding: utf-8 -*- import sys import ...

  9. Linux 下操作GPIO(两种方法,驱动和mmap)(转载)

    目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...

随机推荐

  1. Base64加密与解密

    import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder; // 将 str进行 BASE64 编码 public static Stri ...

  2. C#笔记(一)常量

    常量必须在声明时初始化 常量的值必须能在编译时用于计算.因此,不能用从一个变量中提取的值来初始化常量. 常量总是静态的.但注意,不必(实际上,是不允许)在常量声明中包含修饰符static .

  3. dirty cow exp

    公司搞底层的改了一下,说做到了几个不死机 /* * (un)comment correct payload first (x86 or x64)! * * $ gcc cowroot.c -o cow ...

  4. [Jenkins]admin用户登陆,提示登陆无效(之前登陆OK,三天没有登陆,突然提示登陆无效,重启无法解决)的解决方法

    问题出现现象: 系统一直正常,突然某天登陆,提示用户无效,无法登陆成功. 问题分析过程: 1.查看日志:/var/log/jenkins/jenkins.log(通过ps -elf | grep je ...

  5. hdu1029

    #include<iostream>#include<string.h>using namespace std;int main(){ int n,i; int t; int ...

  6. 让webstorm支持新建.vue文件

    1. 首先安装vue插件,安装方法: file-->setting  -->  plugin  ,点击plugin,在内容部分的左侧输入框不用输入任何东西,直接点击下图中的按钮. 如下图所 ...

  7. Pass和ClassPath变量配置

    1.pass环境变量配置的是可执行性文件bin目录,是为了在任意盘符下都可以运行javac.exe和java.exe所配置的. 2.classpath环境变量记录的是java类运行文件所在的目录,一般 ...

  8. google谷歌翻译插件-网页一键翻译

    上个月转载的一篇博文,是推荐的四款非常实用的翻译插件,这几天看这个chrome插件网首页有新增了一个google谷歌翻译插件.我能说实话,这款插件比之前推荐的4款翻译插件更好用吗?也不能完全说是更好用 ...

  9. Conversion to Dalvik format failed with error 1(android)

    1.如果不修改android sdk版本,则使用project clean 命令作用于某工程即可. (该处理方式只是在高版本中兼容了低版本工程,未真正意义上的升级) 2.如果修改android sdk ...

  10. c3p0私有属性checkoutTimeout设置成1000引发的调试错误:

    checkoutTimeout设置成1000引发的调试错误: org.mybatis.spring.MyBatisSystemException: nested exception is org.ap ...