镜缘浮影 小人本住在 苏州的城外 家里有屋又有田 生活乐无边

进程 (二).exec

2017-01-09
wilmosfang
c
原文地址 http://soft.dog/2017/01/09/c-fork-02/

前言

UNIX/Linux 是多任务的操作系统,那如何进行多任务处理呢,就是通过多个进程分别处理不同事务来实现

事实上一颗单核CPU,在一个时刻里只能处理一条指令,所以在微观的世界里只可能有一个进程正在运行,那为什么是多任务的操作系统呢,那是由于操作系统将CPU时间分成很多的小时间片,并且将这些时间片分配给不同的任务,然后根据特定的方法在不同任务间进行快速的轮转(每一次切换任务都会对当前任务的进展进行保存,然后提取出下一个任务之前保存的进展,这个切换过程是有一定CPU开销的),而相对于计算机,人的速度非常慢,这样从宏观来看,给人的感觉就好像很多事务在同时推进一样,从而达到多任务或并行处理的效果,而多核的CPU就可以真实地进行并行处理,就好像多条流水线同时开工,在这里每个任务都可以看作是一个进程

上一篇中使用fork进行了子进程的创建,这时子进程还是一份父进程的拷贝,如果要让子进程可以完成父进程不一样的功能,就要用到进程的替换

进程的替换是通过 exec 函数族来实现的

这里分享一下我在学习进程过程中的笔记和心得


概要


代码示例

要求

  • 1.父进程(程序名process)产生一个子进程,用程序(z)替换

  • 2.父进程等待子进程结束后,父进程才能结束

process进程:process.c

要求:里面sleep 3秒,要求打印出子进程的PID。

z进程:z.c

要求:每2秒打印一句提示语,循环5次。

代码示例

z.c

#include <stdio.h>
#include <unistd.h> //sleep函数在这里面声明

int main()
{
  int i=0;
  for(i=0;i<5;i++)
  {
    sleep(2);
    printf("%d loop...(there are %d times left)\n",i+1,5-i-1);
  }
  return 0;
}

小程序编译测试

emacs@ubuntu:~/c$ alias  gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc z.x z.c
emacs@ubuntu:~/c$ ./z.x
1 loop...(there are 4 times left)
2 loop...(there are 3 times left)
3 loop...(there are 2 times left)
4 loop...(there are 1 times left)
5 loop...(there are 0 times left)
emacs@ubuntu:~/c$

编译执行过程中没有报错,从结果来看,符合预期

下面是关键的主程序

process.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h> //exit 函数在里面声明

int main()
{
  pid_t fret=0; //创建一个变量用来存放fork返回值

  fret=fork(); 
  if(0 < fret) //如果pe的值大于0,就代表为父进程,fret的值就是子进程的进程号
  {
    int pid=0,status=0;
    
    sleep(3);
    printf("this is the parent process(%d),wait for child(%d)...\n",getpid(),fret); 
    pid = wait(&status); //使用阻塞模式等待子进程退出
    if(-1 !=  pid ) printf("this is the father process, my pid is %d, child process is %d, the wait return value is %d \n",getpid(),fret,WEXITSTATUS(status)); //再次显示父子进程PID,和子进程退出的状态
    else printf("wait error\n");
  }
  else if(0 == fret) //fork返回值为0的时候代表子进程
  {
    char *s="/home/emacs/c/z.x";  //定义一个字符指针存放可执行程序的路径
    char *c="z.x"; //定义一个字符指针存放可执行文件名
    printf ("this is child, pid is :%d, my father pid is %d\n",getpid(),getppid()); //显示父子进程PID信息
    execl(s,c,(char *)0); //进行进程替换, use NULL instead of (char *)0
  } 
  else 
  {
    printf("fork error\n");
    exit(-1); //直接退出,并且将退出状态设置为-1
  }

  return 0;
}

编译执行

emacs@ubuntu:~/c$ alias gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc process.x process.c
emacs@ubuntu:~/c$ ./process.x 
this is child, pid is :7588, my father pid is 7587
1 loop...(there are 4 times left)
this is the parent process(7587),wait for child(7588)...
2 loop...(there are 3 times left)
3 loop...(there are 2 times left)
4 loop...(there are 1 times left)
5 loop...(there are 0 times left)
this is the father process, my pid is 7587, child process is 7588, the wait return value is 0 
emacs@ubuntu:~/c$

编译执行过程中没有报错,从结果来看,符合预期(当中有如预期一样的停顿,并且执行的先后顺序符合期望)


exec函数族

在头文件中,我们通过层层追溯的方式可以找到一个类型的定义

这里我们来看看 exec 究竟是什么

root@ubuntu:/usr/include# grep int unistd.h  | grep exec 
extern int execve (__const char *__path, char *__const __argv[],
extern int fexecve (int __fd, char *__const __argv[], char *__const __envp[])
extern int execv (__const char *__path, char *__const __argv[])
extern int execle (__const char *__path, __const char *__arg, ...)
extern int execl (__const char *__path, __const char *__arg, ...)
extern int execvp (__const char *__file, char *__const __argv[])
extern int execlp (__const char *__file, __const char *__arg, ...)
extern int execvpe (__const char *__file, char *__const __argv[],
root@ubuntu:/usr/include#

从中随便挑出几个来看

unistd.h 文件中

/* Execute PATH with arguments ARGV and environment from `environ'.  */
extern int execv (__const char *__path, char *__const __argv[])
     __THROW __nonnull ((1));

/* Execute PATH with all arguments after PATH until a NULL pointer,
   and the argument after that for environment.  */
extern int execle (__const char *__path, __const char *__arg, ...)
     __THROW __nonnull ((1));

/* Execute PATH with all arguments after PATH until
   a NULL pointer and environment from `environ'.  */
extern int execl (__const char *__path, __const char *__arg, ...)
     __THROW __nonnull ((1));

/* Execute FILE, searching in the `PATH' environment variable if it contains
   no slashes, with arguments ARGV and environment from `environ'.  */
extern int execvp (__const char *__file, char *__const __argv[])
     __THROW __nonnull ((1));

大体上分三类:

  • e 可以使用系统默认的环境变量
  • p 可以使用系统PATH路径中的程序
  • l 逐个参数列举
  • v 将所有参数整体构造指针数组传递

fork,sleep,getpid,getppid 原型

unistd.h 中包含 fork,sleep,getpid,getppid 的函数原型

/* Clone the calling process, creating an exact copy.
   Return -1 for errors, 0 to the new process,
   and the process ID of the new process to the old process.  */
extern __pid_t fork (void) __THROW;
/* Make the process sleep for SECONDS seconds, or until a signal arrives
   and is not ignored.  The function returns the number of seconds less
   than SECONDS which it actually slept (thus zero if it slept the full time).
   If a signal handler does a `longjmp' or modifies the handling of the
   SIGALRM signal while inside `sleep' call, the handling of the SIGALRM
   signal afterwards is undefined.  There is no return value to indicate
   error, but if `sleep' returns SECONDS, it probably didn't work.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern unsigned int sleep (unsigned int __seconds);
/* Get the process ID of the calling process.  */
extern __pid_t getpid (void) __THROW;

/* Get the process ID of the calling process's parent.  */
extern __pid_t getppid (void) __THROW;

fork 和 vfork

unistd.h 中包含 fork,vfork 的函数原型

/* Clone the calling process, creating an exact copy.
   Return -1 for errors, 0 to the new process,
   and the process ID of the new process to the old process.  */
extern __pid_t fork (void) __THROW;
/* Clone the calling process, but without copying the whole address space.
   The calling process is suspended until the new process exits or is
   replaced by a call to `execve'.  Return -1 for errors, 0 to the new process,
   and the process ID of the new process to the old process.  */
extern __pid_t vfork (void) __THROW;

它们都是克隆一份主调进程,如果成功就返回子进程的进程ID给父进程,返回0给子进程,出错就返回-1

区别是在内存中vfork是进行COW(写时复制)的,fork是全部拷贝,因此vfork速度会更快,更省空间


wait,waitpid

/* Wait for a child to die.  When one does, put its status in *STAT_LOC
   and return its process ID.  For errors, return (pid_t) -1.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern __pid_t wait (__WAIT_STATUS __stat_loc);
/* Wait for a child matching PID to die.
   If PID is greater than 0, match any process whose process ID is PID.
   If PID is (pid_t) -1, match any process.
   If PID is (pid_t) 0, match any process with the
   same process group as the current process.
   If PID is less than -1, match any process whose
   process group is the absolute value of PID.
   If the WNOHANG bit is set in OPTIONS, and that child
   is not already dead, return (pid_t) 0.  If successful,
   return PID and store the dead child's status in STAT_LOC.
   Return (pid_t) -1 for errors.  If the WUNTRACED bit is
   set in OPTIONS, return status for stopped children; otherwise don't.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern __pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options);

从上面的描述可以知道

wait(&status) 相当于 waitpid(-1,&status,0)

实际上Linux 内部在实现wait函数时直接调用的就是waitpid函数

status 是用来存放返回值的,一般不是直接使用,而是通过宏来进行解析,例如 WEXITSTATUS(status)

stdlib.h 头文件中有如下定义

/* Define the macros <sys/wait.h> also would define this way.  */
# define WEXITSTATUS(status)    __WEXITSTATUS (__WAIT_INT (status))
# define WTERMSIG(status)       __WTERMSIG (__WAIT_INT (status))
# define WSTOPSIG(status)       __WSTOPSIG (__WAIT_INT (status))
# define WIFEXITED(status)      __WIFEXITED (__WAIT_INT (status))
# define WIFSIGNALED(status)    __WIFSIGNALED (__WAIT_INT (status))
# define WIFSTOPPED(status)     __WIFSTOPPED (__WAIT_INT (status))

总结

以下这些函数可以进行进程创建和简单的管理

  • fork
  • waitpid/wait
  • exec*

通过各方面资料弄懂其参数的意义和返回值的类型,是熟练掌握的基础

原文地址 http://soft.dog/2017/01/09/c-fork-02/

评论