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

进程 (一).fork

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

前言

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

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

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


概要


代码示例

要求

将图中的流程图转换成程序

process.png

代码示例

#include <stdio.h>
#include <unistd.h> //fork,sleep,getpid,getppid 等函数的声明都在这个头文件里
#include <sys/wait.h> //waitpid, WNOHANG的函数声明和宏定义在这个头文件里

int main()
{
  pid_t pe; //定义一个pid类型的变量
  pe=fork(); //调用fork函数创建新进程,并将返回值存入pe变量中,这个过程成功后就会多出一个进程,被派生出来的进程称为子进程,pe也会多出一份拷贝,通过pe的值可以判断身处在哪一个之中
  if(0 < pe) //如果pe的值大于0,就代表为父进程,pe的值就是子进程的进程号
  {
    int pid,status;

    while (0 ==(pid = waitpid(-1,&status,WNOHANG)))sleep(1); //进行循环检测,如果子进程没有退出(waitpid的返回值为0就代表子进程还没有退出),就进行睡觉,睡觉1秒
    if(-1 !=  pid ) printf("this is the father process, my pid is %d, child process is %d, child exit status is: %d\n",getpid(),pe,WEXITSTATUS(status)); //正常情况下pid为正值,应为子进程的进程号,这时将pid,cpid和子进程的退出状态进行打印
    else perror("waitpid"); //如果为-1,那么就是出错,进行提醒
  }
  else if(0 == pe) //fork返回值为0的时候代表子进程
  {
    printf ("this is child, pid is :%d, my father pid is %d\n",getpid(),getppid()); //将pid,ppid进行打印
    sleep(5); //沉睡5秒
    return 123; //退出的状态码为123,这个值的范围在0~256
  }
  else //fork返回值为负的时候代表调用出错
  {
    perror("fork"); //进行提醒
    return -1;
  }

  return 0;
}

编译执行

emacs@ubuntu:~/c$ alias gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc forkprocess.x forkprocess.c 
emacs@ubuntu:~/c$ ./forkprocess.x 
this is child, pid is :7244, my father pid is 7243
this is the father process, my pid is 7243, child process is 7244, child exit status is: 123
emacs@ubuntu:~/c$ 

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


pid_t 的定义

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

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

emacs@ubuntu:/usr/include$ grep pid_t sys/types.h
#ifndef __pid_t_defined
typedef __pid_t pid_t;
# define __pid_t_defined
emacs@ubuntu:/usr/include$ grep include sys/types.h
#include <features.h>
#include <bits/types.h>
#include <time.h>
#include <stddef.h>
# include <endian.h>
# include <sys/select.h>
# include <sys/sysmacros.h>
# include <bits/pthreadtypes.h>
emacs@ubuntu:/usr/include$ grep pid_t bits/types.h
__STD_TYPE __PID_T_TYPE __pid_t;	/* Type of process identifications.  */
emacs@ubuntu:/usr/include$ grep include bits/types.h
 * Never include this file directly; use <sys/types.h> instead.
#include <features.h>
#include <bits/wordsize.h>
#include <bits/typesizes.h>	/* Defines __*_T_TYPE macros.  */
emacs@ubuntu:/usr/include$ grep __PID_T_TYPE bits/typesizes.h
#define __PID_T_TYPE		__S32_TYPE
emacs@ubuntu:/usr/include$ grep __S32_TYPE * -r 
bits/typesizes.h:#define __PID_T_TYPE		__S32_TYPE
bits/typesizes.h:#define __DADDR_T_TYPE		__S32_TYPE
bits/typesizes.h:#define __KEY_T_TYPE		__S32_TYPE
bits/typesizes.h:#define __CLOCKID_T_TYPE	__S32_TYPE
bits/types.h:#define	__S32_TYPE		int
emacs@ubuntu:/usr/include$ 

从中可知整个定义链是这样的

pid_t=>__pid_t=>__PID_T_TYPE=>__S32_TYPE=>int

pid_t 实际上就是 int

那也就意味着可以直接使用int类型来替代 pid_t ,只是使用 pid_t 会更直观


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))

WNOHANG

linux/wait.h

#ifndef _LINUX_WAIT_H
#define _LINUX_WAIT_H

#define WNOHANG         0x00000001
#define WUNTRACED       0x00000002
#define WSTOPPED        WUNTRACED
#define WEXITED         0x00000004
#define WCONTINUED      0x00000008
#define WNOWAIT         0x01000000      /* Don't reap, just poll status.  */

#define __WNOTHREAD     0x20000000      /* Don't wait on children of other threads in this group */
#define __WALL          0x40000000      /* Wait on all children, regardless of type */
#define __WCLONE        0x80000000      /* Wait only on non-SIGCHLD children */

/* First argument to waitid: */
#define P_ALL           0
#define P_PID           1
#define P_PGID          2


#endif

waitpid的参数options是一个或多个标致符按位“或”的结果


总结

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

  • fork
  • waitpid/wait

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

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

评论