Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5607203
  • 博文数量: 922
  • 博客积分: 19333
  • 博客等级: 上将
  • 技术积分: 11226
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-27 14:33
文章分类

全部博文(922)

文章存档

2023年(1)

2020年(2)

2019年(1)

2017年(1)

2016年(3)

2015年(10)

2014年(17)

2013年(49)

2012年(291)

2011年(266)

2010年(95)

2009年(54)

2008年(132)

分类: LINUX

2012-01-15 20:43:18

++++++APUE读书笔记-08进程控制(07)++++++

 

10、exec
================================================
 当调用exec函数的时候,进程会完全被exec所执行的可执行文件所对应的新的程序所替代,从新程序的main函数开始执行(exec后面的语句已经无法再执行到了)。注意使用exec并没有创建进程,所以进程的PID并没有改变,exec仅仅替换当前进程的text,data,heap,和stack段为磁盘中新程序相应的段。
 有6中exec函数,这些函数构成进程控制的基本要素,我们一般就把这些exec函数统称为exec函数。使用fork,我们可以创建一个进程;使用exec,我们可以初始化一个程序;使用exit和wait,我们可以指定和获取退出进程的退出码(以及同步等)。这是我们进行进程控制的基本函数,使用这些函数我们可以构造出一些其他的函数,例如popen,system等。
 #include
 int execl(const char *pathname, const char *arg0,... /* (char *)0 */ );
 int execv(const char *pathname, char *const argv []);
 int execle(const char *pathname, const char *arg0, .../* (char *)0,  char *const envp[] */ );
 int execve(const char *pathname, char *const argv[], char *const envp []);
 int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
 int execvp(const char *filename, char *const argv []);

 (1) 这些函数的第一个不同的地方就是,前四个函数将pathname作为参数,后两个函数把filename作为参数。
 当指定filename的参数的时候,
 a)如果filename包含了一个slash,那么就把它当作pathname。
 b)否则,会在PATH回敬变量中指定的目录搜索filename对应的可执行文件。
 PATH环境变量包含一系列以冒号分割的目录,也就是路径前缀。例如,
 PATH=/bin:/usr/bin:/usr/local/bin/:.
 这个name=value形式的环境变量name是PATH,value指定了四个搜索目录,最后一个"."代表当前目录(实际一个0长度的前缀也表示当前目录,指定的时候可以使用一个冒号在value的开始,或者两个冒号构成一列,或者一个在value结尾的冒号。)
 一般不要包含当前目录,因为这样会有安全隐患。
 如果execlp或者execvp使用路径前缀找到了一个可执行文件,但是这个文件和机器链接的时候的可执行文件格式不一样,那么这个函数假设文件是一个shell脚本,然后尝试调用/bin/sh,一这个文件名作为参数,来运行这个脚本。
 (2)另外一个不同的地方是参数列表的传递方式(l表示list,v表示vector)。
 函数execl,execlp,和execle要求每个传递到这个程序的命令行参数是一个独立的参数,我们把这些参数的结尾添加一个null指针。对于其他的三个函数(execv,execvp,execve),我们需要建立一个指针数组,其指针指向相应参数,然后这个数组的地址作为这三个函数的参数。
 在使用ISO C声明之前,正常对函数execl,execle,和execlp展示的命令行参数形式是:
 char *arg0, char *arg1, ..., char *argn, (char *)0
 这个标准要求命令行的最后一个参数是一个null 指针。如果值是0那么我们要把它显示地转换成char*类型的,否则会被当做一个int参数来看代。如果整数的大小和char*这个标准要求命令行的最后一个参数是一个null 指针。如果int变量的大小和char的大小不一样,那么实际传递给exec函数的参数会发生错误。
 (3)最后一个不同的地方就是对新程序的环境变量的传递。
 execle和execve函数允许我们传递一个指向环境变量字符串的一个指针数组。另外的四个函数,使用调用进程的environ变量来为新程序拷贝一份已经存在的环境变量(回顾前面的关于环境变量的内容可知,如果我们系统支持setenv和putenv函数,那么我们可以在不影响父进程的情况下来修改子进程的环境变量)。一般来说,一个进程允许它的环境变量被传播到它的子进程,但是在一个时候,进程需要对一个子进程指定特定的环境变量,例如login程序初始化login shell的时候。一般,login创建一个指定的仅定义了几个变量环境变量列表然后让我们通过shell的启动文件在我们登录的时候添加环境变量。
 在使用ISO C声明之前,对execle的相应(环境变量列表)参数的展示形式是:
 char *pathname, char *arg0, ..., char *argn, (char *)0, char *envp[]
 由这个标准可知最后一个参数是环境变量指针列表的数组地址。 ISO C的声明就没有这样来表示,因为所有的命令行参数,空指针,以及环境变量列表指针使用(...)来进行表示了。

 这六个exec函数的参数很难记忆,但是考虑一下函数名字可以帮助我们记忆。字母p表示函数会使用PATH来接受一个filename参数,进而寻找可执行文件;字母l表示函数会接受一个参数列表,它和字母v相对;字母v表示一个argv[](元素为参数指针的指针数组);字母e表示函数接受一个envp[]数组(是指向环境变量列表的指针数组),和使用当前的环境变量相对。下面通过一个表格列出了所有exec函数的区别。

 六种exec函数的区别
+-----------------------------------------------------------------------------+
|    Function     | pathname | filename |   Arg   | argv[] | environ | envp[] |
|                 |          |          |  list   |        |         |        |
|-----------------+----------+----------+---------+--------+---------+--------|
| execl           |    •     |          |    •    |        |    •    |        |
|-----------------+----------+----------+---------+--------+---------+--------|
| execlp          |          |    •     |    •    |        |    •    |        |
|-----------------+----------+----------+---------+--------+---------+--------|
| execle          |    •     |          |    •    |        |         |   •    |
|-----------------+----------+----------+---------+--------+---------+--------|
| execv           |    •     |          |         |   •    |    •    |        |
|-----------------+----------+----------+---------+--------+---------+--------|
| execvp          |          |    •     |         |   •    |    •    |        |
|-----------------+----------+----------+---------+--------+---------+--------|
| execve          |    •     |          |         |   •    |         |   •    |
|-----------------+----------+----------+---------+--------+---------+--------|
| (letter in      |          |    p     |    l    |   v    |         |   e    |
| name)           |          |          |         |        |         |        |
+-----------------------------------------------------------------------------+


 每个系统的参数列表和环境变量列表的长度是有限制的,在第2章已经说明,限制由ARG_MAX给出。在POSIX.1中,这个值必须至少是4096字节。有时候我们在敲入文件名的时候会遇到这个限制,例如:
 grep getrlimit /usr/share/man/*/*
 可能会提示shell错误,说是参数过长.
 以前,在System V上面这个参数限制是5120字节。旧版本的BSD系统的限制是20480字节,当前系统的限制更高。
 为了避免遇到参数列表大小的限制,我们使用xargs命令把长的参数列表分割,例如上面的命令就可以写成:
 find /usr/share/man -type f -print | xargs grep getrlimit
 如果我们系统上面的man pages是压缩的,我们可以运行:
 find /usr/share/man -type f -print | xargs bzgrep getrlimit
 这里,使用了type -f选项是为了限定查找的文件类型是正常文件,因为grep无法在命令中进行搜索,我们需要避免一些不必要的错误。

 除了exec调用进程的进程ID不会改变之外,新程序还有一些其他的属性也会被继承过来,具体参见参考资料。
 新程序对打开的文件的处理取决于相应文件描述番号的close-on-exec标志。每个打开的文件描述符号有一个close-on-exec标志,如果标志被设置了,那么文件描述符在调用exec的时候会被关闭,否则执行exec的时候也会保持打开的状态。默认这个标志是没有设置的也就是说文件描述符号在执行exec的时候是打开的状态,除非我们使用了fcntl来设置这个close-on-exec标志。
 POSIX.1规定打开的目录流(参见opendir)在执行exec的时候被关闭。这一点是通过opendir函数调用fcntl设置打开的目录流中的相应文件描述符号的close-on-exec标志来做到的。
 需要注意的是调用exec之后,real user ID和real group ID是保持不变的,但是effective ID却是变化的,这取决于相应的要执行的文件的set-user-ID和set-group-ID位。如果这个位被设置了,那么effective user ID就变成了这个程序文件相应的属主ID了。否则,effective 就不变(不会设置成real user ID).group ID的情况处理类似。

 六个exec函数之间的关系:
 在许多unix系统中,这6个函数中只有一个(execve),是内核中的系统调用。其他的5个都是库函数,是调用这个系统调用来实现的。我们在参考资料中给出了这些函数的调用关系。
 图形在这里就省略了,图形大概描述的情况就是:
 execlp--(build argv)-->execvp--(尝试每一个path前缀)-->execv--(使用环境变量,执行系统调用)-->execve。
 execl--(build argv)-->execv--(使用环境变量,执行系统调用)-->execve。
 exece--(build argv,执行系统调用)-->execve。
 上面的规律是:
 先l变v(如果有的话,通过build argv)
 再去p(如果有的话,通过尝试PATH前缀)
 再用e(如果有的话,通过使用环境变量)

 举例:
 书中给出例子如下:
 ...
 if (execle("/home/sar/bin/echoall", "echoall", "myarg1",
    "MY ARG2", (char *)0, env_init) < 0)
 ...
 if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
 这里,我们先使用execle,它需要被传递一个路径名称以及一个环境变量数组指针;然后我们第二个方法是execlp,它只需要被传递一个文件名称,因为它会在PATH环境变量里面寻找这个文件名称的路径前缀。
 需要注意的是,这里我们设置的arg0的地方,它实际上可以是任何字符串(例如可以是程序名称,也可以是路径名称),有些shell执行可执行程序的时候,把这个args[0]设置成为了全路径.login会将args[0]前面加上一个破折号前缀,表示是登陆的shell。我这ps之后看到login如下,不知道是不是说的这个:
 root      2683  0.0  0.2   2984  1156 tty1     Ss+  08:30   0:00 /bin/login --

参考:

 

 

阅读(559) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~