Linux进程线程学习笔记:运行新程序

                                        周银辉

上一篇中我们说到,当启动一个新进程以后,新进程会复制父进程的大部份上下文并接着运行父进程中的代码,如果我们使新进程不运行原父进程的代码,转而运行另外一个程序集中的代码,这就相当于启动了一个新程序。这里的代码我们可以理解成一个可执行程序。

所以,要运行一个新程序,需要最基本的两步:

1,创建一个可运行程序的环境,也就是进程。

2,将环境中的内容替换成你所希望的,也就是用你希望运行的可执行文件去覆盖新进程中的原有映像,并从该可执行文件的起始处开始执行。

要做到第一点,非常简单,fork函数就可以(参考上一篇) ,要做到第二点,则可以利用exec函数族。

exec是一族函数的简称,包含在<unistd.h>中它们作用都一样,用一个可执行文件覆盖进程的现有映像,并转到该可执行文件的起始处开始执行。

原型如下:

int execl(const char *path, const char *arg0, ... /*, (char *)0 */);

int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);

int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execve(const char *path, char *const argv[], char *const envp[]);

我们先以最简单的execl函数为例,其他的大同小异,其第一个参数path是可执行文件的路径,是绝对路径;从arg0参数开始及后面所有的是你要传递给可执行文件的命令行参数,值得注意的是,arg0是可执行文件本身(还记得C语言中老师讲main函数参数列表时所说的么),当然,不传程序本身或传一些乱七八糟的值并不代表不能通过编译或不能运行,只不过,如果可执行文件要用到arg0时会产生一些迷惑;最后有一个注释/*, (char*)0 */是提醒我们最后一个参数应该传空字符串。如何函数运行成功,则不会有任何返回值,否则返回-1,而具体的错误号会被设置在errno,errno是一个全局变量,用于程序设置错误号,跟win32的getLastError函数类似。

看下面的例子:

#include <stdio.h>
#include <unistd.h>
int main ()
{
    printf("app start...\n");
    execl("/bin/ls", "/bin/ls", "-l",NULL);
    printf("app end\n");
return 0;
}

我们运行了bin目录下的ls程序,参数arg0时ls程序本身路径,arg1为-l,使得其以列表的形式列举当前目录,在我的计算机上程序输出如下:

app start...
total 12
-rw-r--r-- 1 zhouyh zhouyh  273 2010-09-06 11:09 temp.c
-rwxr-xr-x 1 zhouyh zhouyh 7175 2010-09-06 11:09 temp.exe

ls程序运行成功了。但注意到了吗?没有输出“app end”这个字符串,原因很简单,我们没有新起进程,而是直接用ls程序覆盖了main函数所在的进程。

那我们接下来,试着用fork吧,以免影响原进程。

#include <stdio.h>
#include <unistd.h>
int main ()
{
    printf("app start...\n");
if(fork() == 0)
    {
        execl("/bin/ls", "/bin/ls", "-l", NULL);
    }
    printf("app end\n");
return 0;
}

我们用fork创建了一个新进程,当其成功创建后(返回值为0),我们用execl来加载ls程序并运行之。

程序的输出如下:

app start...
app end
zhouyh@ubuntu:~/Documents$ total 12
-rw-r--r-- 1 zhouyh zhouyh  229 2010-09-06 15:59 temp.c
-rwxr-xr-x 1 zhouyh zhouyh 7211 2010-09-06 16:00 temp.exe

程序的所有输出都OK了,但有一点可能和我们想象的不一样,那就是“app end”这个字符串很早就输出了而不是在最后,其实这并没有错,“app end” 是main函数所在的程序(temp.exe)即将结束时输出的,而列举文件目录的ls却完全在另外一个进程中,两个异步执行的进程,他们没有谁先谁后结束可言。

如果我们希望所有工作完成之后,即ls也执行玩以后,才输出“app end”,那么可以使用wait 以及waitpid函数,这里简单说一下wait,具体的会在“Linux进程线程学习笔记:进程控制”中讲。
pid_t wait (int * status); //包含在 <sys/wait.h> 中

wait函数讲当前进程休眠,直到该进程的某个子进程结束或者有特定的信号来唤醒。如果子进程正常结束,则讲子进程的进程id(pid)作为返回值,发生错误则返回-1,而status参数讲传出子进程的结束状态值。
针对刚才的例子,可以参考下面的代码:

#include <stdio.h> //for printf(const char)
#include <unistd.h> //for fork()
#include <sys/wait.h> //for wait(int* status)
int main ()
{
    printf("app start...\n");
if(fork() == 0)
    {
        execl("/bin/ls", "/bin/ls", "-l", NULL);
    }
int status;
    wait(&status);
    printf("app end\n");
return 0;
}

程序输出如下:

app start...
total 12
-rw-r--r-- 1 zhouyh zhouyh  337 2010-09-06 16:22 temp.c
-rwxr-xr-x 1 zhouyh zhouyh 7247 2010-09-06 16:22 temp.exe
app end

好了,现在回过头来看除execl外的其他几个函数 :

int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);

execlp和execl差不多,但区别在于前者会去系统环境变量查找file所指的程序的位置,所以如果通过环境变量能找到可执行文件,则file可以不是绝对路径了,比如 execlp("ls", "ls", "-l", NULL);

int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[]*/);

与execlp不同的是,其最后一个参数作为你自定义的环境变量参数传进去,而不是查找系统环境变量

char *env[] = { "HOME=/usr/home", "LOGNAME=home",(char *)0 };

execle("/bin/ls", "ls", "-l", NULL, env);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execve(const char *path, char *const argv[], char *const envp[]);

这三个函数和前面的三个类似,函数名由后缀l变成了v,其表达的含义是参数不再用参数列表传递而是用一个参数数组argv[],当然,数组最后一个元素也必须是char* 0

名字这么相近的函数,感觉好容易混淆,那么就从l,v,p,e 这样的后缀来区分吧:
l:参数为一个逗号分隔的参数列表,并以char* 0作为列表结尾

v: 参数为字符串数组,数组的最后一个元素为char* 0

p: 可以通过系统环境变量查找文件位置

e:调用者显示传入环境变量

Linux进程线程学习笔记:运行新程序的更多相关文章

  1. linux 进程学习笔记-运行新进程

    我们知道,当用fork启动一个新进程以后,新进程会复制父进程的大部份内存空间并接着运行父进程中的代码,如果我们使新进程不运行原父进程的代码,转而运行另外一个程序集中的代码,这就相当于启动了一个新程序. ...

  2. linux进程的学习笔记(未完)

    1. 进程是程序执行的一个实例,如果16个用户同时运行vi,那么有16个独立的进程,尽管它们共享同一个可执行代码,问题在于FreeRTOS这种系统,是否可以建2个相同的任务,需要注意什么?在linux ...

  3. Linux进程间通信IPC学习笔记之管道

    基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ...

  4. Linux进程间通信IPC学习笔记之有名管道

    基础知识: 有名管道,FIFO先进先出,它是一个单向(半双工)的数据流,不同于管道的是:是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)有一个与路径名关联的名 ...

  5. C#线程学习笔记九:async & await入门二

    一.异步方法返回类型 只能返回3种类型(void.Task和Task<T>). 1.1.void返回类型:调用方法执行异步方法,但又不需要做进一步的交互. class Program { ...

  6. Linux下iptables学习笔记

    Linux下iptables学习笔记 在Centos7版本之后,防火墙应用已经由从前的iptables转变为firewall这款应用了.但是,当今绝大多数的Linux版本(特别是企业中)还是使用的6. ...

  7. Linux防火墙iptables学习笔记(三)iptables命令详解和举例[转载]

     Linux防火墙iptables学习笔记(三)iptables命令详解和举例 2008-10-16 23:45:46 转载 网上看到这个配置讲解得还比较易懂,就转过来了,大家一起看下,希望对您工作能 ...

  8. linux基础命令学习笔记(二)

    linux基础命令学习笔记(二) 1.kill :终止进程  kill pid (唯一标示一个进程) kill -9  强制终止  kill -15 命令未结束不能终止 # ps aux 查看所有进程 ...

  9. 兄弟连Linux运维学习笔记

    最新经典linux运维兄弟连Linux运维学习笔记... --------------- 全程1.5倍播放.加油我一定可以学完Linux----------------------Unix与Linux ...

随机推荐

  1. error: undefined reference to 'property_set (转载)

    转自:http://blog.csdn.net/u011589606/article/details/23474241 in the cpp file, please include #include ...

  2. input的type等于mage标签引发的页面提交!

    主要原因是使用了<input type="image",而且放在了<form中. <input type="image"标签类似<inp ...

  3. HttpPostedFileBase 基类

    public void uploadDocMentSave(string Type)        { if (Request.Files.Count > 0)            { Htt ...

  4. ANDROID 开发之 SQLite

    SQLite简介 Google为Andriod的较大的数据处理提供了SQLite,他在数据存储.管理.维护等各方面都相当出色,功能也非常的强大.SQLite具备下列特点: 1.轻量级 使用 SQLit ...

  5. 6CSS之文本

    CSS文本:文本缩进(text-indent).文本对齐(text-align).文本修饰(text-decoration).文本大小写(text-transform).字符距离(letter-spa ...

  6. easyui datagrid 高度布局自适应

    最近在把以前写的一个项目改成用easyui做前端.过程中遇到了不少问题.其中一个就是datagrid不能很好的布局.想了好多办法都有局限.最后想到会不会是布局(easyui-layout)的问题,经过 ...

  7. [ AHOI 2008 ] Meet

    \(\\\) \(Description\) 一棵\(N\)个节点的树,每条边权都为\(1\). \(M\)组询问,每次给出三个点\(A_i,B_i,C_i\),求从三个点分别出发,移动到同一个点的路 ...

  8. [ NOIP 2014 ] TG

    \(\\\) \(Day\ 1\) \(\\\) \(\#\ A\) \(Rps\) 定义五种方案的石头剪刀布游戏,两人共进行\(N\)局游戏,已知两人各自的循环节和具体方案,胜者得\(1\)分,败者 ...

  9. android ormlite 清空表

    delete from TableName; //清空数据 update sqlite_sequence SET seq = where name ='TableName';//自增长ID为0 Sam ...

  10. 微信 之jsapi实现支付

    一.微信公众号号后台支付配置 附微信支付参考文档:https://pay.weixin.qq.com/wiki/doc/api/index.html 二.微信支付类封装 该类可以实现付款码支付.JSA ...