进程篇(3: 基本进程控制:进程的执行)--请参照本博客“操作系统”专栏

本篇博文的原理部分参见我的另一篇博文: http://www.cnblogs.com/jiangheng/p/3759914.html

1. 进程竞争条件:
  当多个进程都企图对共享数据进行某种处理,而最终的结果又取决于进程运行的顺序时,则我们认为发生了"竞争条件"(race condition)。如果fork之后的某种逻辑显示或隐式的依赖于在fork之后是父进程先运行或子进程先运行,那么这个fork函数就会是竞争条件活跃的竞争地。在这种情况下,我们通常不能预料哪一个进程先运行,即使知道了哪一个进程先运行,那么该进程开始运行之后,所发生的事情也是依赖于系统负载以及内核调度算法

  在父子进程中,常常出现下述状况。在调用fork之后,父子进程都有一些事情要做。在这里,我们要求进程在完成它的一套初始化操作之后要通知对方,并且在继续运行之前,要等待另一方完成其初始化操作。

  下面是这个方案的代码描述:

#include "apue.h"  /* some header file needes  */

TELL_WAIT();        /* set things up for TELL_xxx & WAIT_xxx  */

if ((pid = fork()) < 0)
{
    err_sys("fork error");
}
else if (pid == 0)
{
     /* child  */
     
     /* child does whatever is necessary...  */
     TELL_PARENT(getppid());     /* tell parent we are down  */
     WAIT_PARENT();                 /* and wait for parent */

     /* and the child continus on this way... */
     exit(0);         
}

/* parent does whatever is necessary */
TELL_CHILD(pid);                      /* tell child we're done */
WAIT_CHILD();                         /* and wait for child */

/* and parent continus on this way...  */
exit(0);    

 下面用实例来说明竞争条件才存在和如何去避免竞争条件的发生:

 1 #include "apue.h"
 2 
 3 static void charatatime(char *);
 4 
 5 int
 6 main(void)
 7 {
 8     pid_t pid;
 9 
10     if((pid = fork()) < 0)
11     {
12         err_sys("fork error");
13     }
14     else if(pid == 0)
15     {
16         charatatime("output from child\n");
17     }
18     else
19     {
20         charatatime("output from parent\n");
21     }    
22     exit(0);
23 }
24 
25 static void charatatime(char *str)
26 {
27     char *ptr;
28     int  c;
29 
30     setbuf(stdout,NULL); /* set unbuffered */
31     for(ptr = str; (c = *ptr++) != 0; )
32         putc(c,stdout);
33 }

下面是多次运行上面程序的结果:

./a.out
output from pareoutput from child
./a.out
output from paroeunttp
ut from child
./a.out
output from pareonutt
put from child

实际输出的结果是可以改变的,也就是因为竞争条件的存在而导致了结果的不确定性!

可以将上述程序做如下修改来避免竞争条件:

 1 #include "apue.h"
 2 
 3 static void charatatime(char *);
 4 
 5 int
 6 main(void)
 7 {
 8     pid_t pid;
 9 
10 +   TELL_WAIT();  
11 
12     if((pid = fork()) < 0)
13     {
14         err_sys("fork error");
15     }
16     else if(pid == 0)
17     {
18 +       WAIT_PARENT();                             /* parent goes first */
19         charatatime("output from child\n");
20     }
21     else
22     {
23         charatatime("output from parent\n");
24 +       TELL_CHILD(pid);
25     }    
26     exit(0);
27 }
28 
29 static void charatatime(char *str)
30 {
31     char *ptr;
32     int  c;
33 
34     setbuf(stdout,NULL); /* set unbuffered */
35     for(ptr = str; (c = *ptr++) != 0; )
36         putc(c,stdout);
37 }    

上面的WAIT和TELL原语将在后面关于进程间通行的章节进一步探讨!

 

2. exec 函数:

  综合叙述: 用fork函数创建子进程之后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程执行的程序完全替换成新程序。

而新程序则是从其main函数开始执行,因为调用exec函数并不创建新进程,所以进程前后的ID并没有改变。exec只是用一个全新的程序替换当前进程的正文,数据堆和栈

。 

  exec函数使得Unix进程控制原语更加完善:

  • fork可以创建新进程;
  • exec可以执行新程序;
  • exit函数和两个wait函数处理终止和等待终止;

下面是linux manpage中对exec函数族的描述:

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                   char *const envp[]);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       execvpe(): _GNU_SOURCE

DESCRIPTION
       The  exec() family of functions replaces the current process image with
       a new process image.(exec 函数族将当前的进程印象替换成另外一个进程印象)  
The functions described in this manual page are front-ends for execve(2)[execve是一个系统调用]. (See the manual page for execve(2) for fur‐ ther details about the replacement of the current process image.) The initial argument for these functions is the name of a file that is to be executed.[这些函数的第一个参数是要执行的文件名] The const char *arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as arg0, arg1, ..., argn. Together they describe a list of one or more pointers to null-termi‐ nated strings that represent the argument list available to the exe‐ cuted program.[这些是要执行的程序的参数] The first argument, by convention, should point to the filename associated with the file being executed. The list of argu‐ ments must be terminated by a NULL pointer, and, since these are vari‐ adic functions, this pointer must be cast (char *) NULL. The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer. The execle() and execvpe() functions allow the caller to specify the environment of the executed program via the argument envp. The envp argument is an array of pointers to null-terminated strings and must be terminated by a NULL pointer. The other functions take the environment for the new process image from the external variable environ in the calling process. Special semantics for execlp() and execvp(): The execlp(), execvp(), and execvpe() functions duplicate the actions of the shell in searching for an executable file if the specified file‐ name does not contain a slash (/) character.[这三个函数的的第一个参数是文件名:
1. 如果filename中包含/,则将其视为路径名;
2. 否则就按PATH环境变量,在PATH所指定的各目录中搜寻可执行文件;
]
The file is sought in the colon-separated list of directory pathnames specified in the PATH envi‐ ronment variable. If this variable isn't defined, the path list defaults to the current directory followed by the list of directories returned by confstr(_CS_PATH). (This confstr(3) call typically returns the value "/bin:/usr/bin".) If the specified filename includes a slash character, then PATH is ignored, and the file at the specified pathname is executed. In addition, certain errors are treated specially. If permission is denied for a file (the attempted execve(2) failed with the error EACCES), these functions will continue searching the rest of the search path. If no other file is found, however, they will return with errno set to EACCES.[权限问题: 如果无法获得一个文件的权限,那么函数就继续在
搜索路径中寻找同名文件,如果找不到其他具有权限的文件,返回EACCES错误].
If the header of a file isn
't recognized (the attempted execve(2) failed with the error ENOEXEC), these functions will execute the shell (/bin/sh) with the path of the file as its first argument. (If this attempt fails, no further searching is done.)
[如果execlp或execvp使用路径前缀中的一个找到了一个可执行文件,但该文件并不是由连接
编译器产生的可执行文件,则认为该文件是一个shell脚本,于是尝试调用/bin/sh,以该filename
作为shell的输入]。 RETURN VALUE The exec() functions
return only if an error has occurred. The return value is -1, and errno is set to indicate the error. ERRORS All of these functions may fail and set errno for any of the errors specified for execve(2).

执行exec的新进程和原进程的继承与修改:

继承下来的:

  • 进程ID和父进程ID
  • 实际用户ID和实际组ID
  • 附加组ID
  • 会话ID
  • 控制终端
  • 闹钟尚且剩余的时间
  • 当前的工作目录
  • 根目录
  • 文件模式创建屏蔽字
  • 文件锁
  • 进程信号屏蔽
  • 未处理的信号
  • 资源限制
  • tms_utime,tms_stime,tms_cutime以及tms_cstime值

可能变化的内容:

对打开文件的处理与每个描述符的执行时关闭(close-on-exec)标志值有关,进程中的每个打开描述符都有一个执行时关闭标志,若此标识符

被设置,那么在执行exec时将关闭该描述符,否则该标识符打开。系统默认操作是执行exec后任然保持描述符的打开;

 

上图是六个exec函数的关系。

 

下面用一个实际程序来演示exec函数:

 1 #include "apue.h"
 2 #include <sys/wait.h>
 3 
 4 char *evn_init[] = {"USER=unknown","PATH=/tmp",NULL};
 5 
 6 int main(void)
 7 {
 8     pid_t pid;
 9 
10     if ((pid = fork()) < 0)
11     {
12         err_sys("fork error");
13     }
14     else if (pid == 0)
15     {
16         /* specify pathname, specify environment */
17         if (execle("/bin/echo","echoall","myarg1","MY ARG2",(char *)0,evn_init) < 0)
18             err_sys("execle error");
19     }
20 
21     if (waitpid(pid,NULL,0) < 0)
22         err_sys("wait error");
23 
24     if ((pid = fork()) < 0)
25     {
26         err_sys("fork error");
27     }
28     else if (pid == 0)
29     {
30         if (execlp("echo","echoall","only 1 arg",(char*) 0) < 0)
31             err_sys("execlp error");
32     }
33     exit(0);
34 }

 

  

 

posted on 2014-06-02 10:04  Dream Catcher(DC)  阅读(200)  评论(0)    收藏  举报

导航