有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在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. java-并发解决方案

    并发产生数据不一致的原因:1.程序共享对象:2.多线程.3.基于1和2,取出来的数据可能不是最新的. 解决方案:只要是原子性操作,就不会出现问题.原子性操作,代表cpu会一直执行这个操作,知道结束. ...

  2. 【锋利的Jquery】读书笔记四

    jquery中的事件及动画 一.事件 页面加载 $(document).ready(function(){xxxxx}) 简写 $(function(){ //do something }) 元素绑定 ...

  3. [SQL基础教程] 4-1 数据的插入(INSERT)

    [SQL基础教程] C4 数据更新 4-1 数据的插入(INSERT) INSERT INSERT INTO <表名>(列1,列2...) VALUES(值1,值2...); 清单 用() ...

  4. Linux Ubuntu 虛擬機系統自定義桌面分辨率且重啓後保持不變

    我用 VMware Workstation 12 Pro 安裝的 Ubuntu MATE Desktop Environment 1.12.1,發現安裝後沒有需要的分辨率,於是安裝 VMware To ...

  5. R和python连接SQL sever 数据库操作

    在R的使用中,为了方便提取数据, 我们经常要进行数据库进行操作,接下来我们尝试使用R进行连接数据. 这里我们使用R中的RODBC进行操作, 首先,我们需要先配置ODBC资源管理器 通过任务管理器或者w ...

  6. MATLAB初体验

    好激动 要入MATLAB的大坑了 恩 很遗憾第一个程序并不是hello world 好 插入代码里并没有MATLAB这个选项 这是一种歧视 x=[:pi/:*pi]; y=sin(x); plot(x ...

  7. android下拉刷新控件 android-pulltorefresh

    运行效果: 介绍:ListView.ViewPager.WevView.ExpandableListView.GridView.(Horizontal)ScrollView.Fragment上下左右拉 ...

  8. CodeForces 687C The Values You Can Make

    $dp$,背包. $f[i][j][s]$表示前$i$个物品,凑出$j$价格的情况下,能否凑出$s$价格,$f[i][j][s]=1$表示能,否则不能. 转移很简单:如果$f[i][j][s]=1$, ...

  9. lda 主题模型--TOPIC MODEL--Gibbslda++结果分析

    在之前的博客中已经详细介绍了如何用Gibbs做LDA抽样.(http://www.cnblogs.com/nlp-yekai/p/3711384.html) 这里,我们讨论一下实验结果: 结果文件包括 ...

  10. 【锋利的Jquery】读书笔记二

    一.jquery选择器 基本选择器 层次选择器 过滤选择器 基本过滤 内容过滤 可见性过滤 属性过滤 子元素过滤 first  : 获取单个元素       $("div:first&quo ...