进程之fork()函数的详细介绍

内容介绍

    本博文主要探讨fork函数以及execuv的用法。

头文件介绍

    下面的是csapp.h头文件,后面的讨论中均只用该头文件来完成程序的编写。
    

/* $begin csapp.h */
#ifndef __CSAPP_H__
#define __CSAPP_H__

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>


/* Default file permissions are DEF_MODE & ~DEF_UMASK */
/* $begin createmasks */
#define DEF_MODE   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK  S_IWGRP|S_IWOTH
/* $end createmasks */

/* Simplifies calls to bind(), connect(), and accept() */
/* $begin sockaddrdef */
typedef struct sockaddr SA;
/* $end sockaddrdef */

/* Persistent state for the robust I/O (Rio) package */
/* $begin rio_t */
#define RIO_BUFSIZE 8192
typedef struct {
    int rio_fd;                /* Descriptor for this internal buf */
    int rio_cnt;               /* Unread bytes in internal buf */
    char *rio_bufptr;          /* Next unread byte in internal buf */
    char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;
/* $end rio_t */

/* External variables */
extern int h_errno;    /* Defined by BIND for DNS errors */ 
extern char **environ; /* Defined by libc */

/* Misc constants */
#define    MAXLINE     8192  /* Max text line length */
#define MAXBUF   8192  /* Max I/O buffer size */
#define LISTENQ  1024  /* Second argument to listen() */

/* Our own error-handling functions */
void unix_error(char *msg);
void posix_error(int code, char *msg);
void dns_error(char *msg);
void app_error(char *msg);

/* Process control wrappers */
pid_t Fork(void);
void Execve(const char *filename, char *const argv[], char *const envp[]);
pid_t Wait(int *status);
pid_t Waitpid(pid_t pid, int *iptr, int options);
void Kill(pid_t pid, int signum);
unsigned int Sleep(unsigned int secs);
void Pause(void);
unsigned int Alarm(unsigned int seconds);
void Setpgid(pid_t pid, pid_t pgid);
pid_t Getpgrp();

/* Signal wrappers */
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
void Sigemptyset(sigset_t *set);
void Sigfillset(sigset_t *set);
void Sigaddset(sigset_t *set, int signum);
void Sigdelset(sigset_t *set, int signum);
int Sigismember(const sigset_t *set, int signum);

/* Unix I/O wrappers */
int Open(const char *pathname, int flags, mode_t mode);
ssize_t Read(int fd, void *buf, size_t count);
ssize_t Write(int fd, const void *buf, size_t count);
off_t Lseek(int fildes, off_t offset, int whence);
void Close(int fd);
int Select(int  n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, 
       struct timeval *timeout);
int Dup2(int fd1, int fd2);
void Stat(const char *filename, struct stat *buf);
void Fstat(int fd, struct stat *buf) ;

/* Memory mapping wrappers */
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
void Munmap(void *start, size_t length);

/* Standard I/O wrappers */
void Fclose(FILE *fp);
FILE *Fdopen(int fd, const char *type);
char *Fgets(char *ptr, int n, FILE *stream);
FILE *Fopen(const char *filename, const char *mode);
void Fputs(const char *ptr, FILE *stream);
size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

/* Dynamic storage allocation wrappers */
void *Malloc(size_t size);
void *Realloc(void *ptr, size_t size);
void *Calloc(size_t nmemb, size_t size);
void Free(void *ptr);

/* Sockets interface wrappers */
int Socket(int domain, int type, int protocol);
void Setsockopt(int s, int level, int optname, const void *optval, int optlen);
void Bind(int sockfd, struct sockaddr *my_addr, int addrlen);
void Listen(int s, int backlog);
int Accept(int s, struct sockaddr *addr, socklen_t *addrlen);
void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

/* DNS wrappers */
struct hostent *Gethostbyname(const char *name);
struct hostent *Gethostbyaddr(const char *addr, int len, int type);

/* Pthreads thread control wrappers */
void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, 
            void * (*routine)(void *), void *argp);
void Pthread_join(pthread_t tid, void **thread_return);
void Pthread_cancel(pthread_t tid);
void Pthread_detach(pthread_t tid);
void Pthread_exit(void *retval);
pthread_t Pthread_self(void);
void Pthread_once(pthread_once_t *once_control, void (*init_function)());

/* POSIX semaphore wrappers */
void Sem_init(sem_t *sem, int pshared, unsigned int value);
void P(sem_t *sem);
void V(sem_t *sem);

/* Rio (Robust I/O) package */
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void rio_readinitb(rio_t *rp, int fd); 
ssize_t    rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t    rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);

/* Wrappers for Rio package */
ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
void Rio_writen(int fd, void *usrbuf, size_t n);
void Rio_readinitb(rio_t *rp, int fd); 
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);

/* Client/server helper functions */
int open_clientfd(char *hostname, int portno);
int open_listenfd(int portno);

/* Wrappers for client/server helper functions */
int Open_clientfd(char *hostname, int port);
int Open_listenfd(int port); 

#endif /* __CSAPP_H__ */
/* $end csapp.h */

fork()函数示例一

 

#include "csapp.h" 
int main()
{
  pid_t pid;
  int x=1;
  pid=fork();
  if(pid==0) {
    printf("child :x=%d\n",++x);
    exit(0);
  }
  printf("parent:x=%d\n",--x);
  exit(0);
}

    例如上面的程序,由于fork()函数比较特殊,执行一次,返回两次。返回两次分别是在父进程和子进程中各返回一个值,在子进程中返回为0,在父进程中返回进程ID,一般为正整数即非零。这样就能根据返回值来确定其在哪个进程中了。如上面的程序,子进程中pid=0,所以执行if语句,子进程会共享父进程的文本/数据/bss段/堆以及用户栈,子进程随后正常终止并且返回码为0,因此子进程不执行后续的共享代码块,因此本程序的输出结果是

parent:x=0
child :x=2

fork()函数示例二

    

#include "csapp.h"

int main()
{
  if(fork()==0) {
    printf("a");
  }
  else {
    printf("b");
    waitpid(-1,NULL,0);
  }
  printf("c");
  exit(0);
}

    此程序是用来检验子进程与父进程的关系。同样再次强调一遍,fork()函数用于新建子进程,子进程具有与父进程相同的用户级虚拟地址空间,包括文本/数据/bss段/堆/用户栈,子进程可以读写任意父进程打开的文件,它们的最大区别是它们有不同的PID。fork函数调用一次,返回两次,一次在父进程中,其返回子进程的PID;在子进程中,fork返回0,因为子进程的PID总是非零的,返回值就提供了一个明确的方法来辨别是在父进程中执行还是在子进程中执行。waitpid()函数是等待子进程终止,若无错误,则返回值为正数。因为在在子进程中,fork()返回0,因此先输出a,并且其共享父进程的代码段,故又输出c;而在父进程中,fork()返回值非零,所以执行else语句,故输出bc。因此本程序的输出结果为

acbc

fork()函数示例三

    

#include "csapp.h"
int main()
{
  int x=1;
  if(fork()==0)
    printf("printf1:x=%d\n",++x);
  printf("printf2:x=%d\n",--x);
  exit(0);
}

    本程序再次演示子进程与父进程的区别。程序中,在子进程中,子进程共享数据x=1,并且fork()返回0,因此if语句被执行,输出printf1:x=2,接着共享后面一部分代码段,因此再输出printf2:x=1;而对于父进程,fork()返回非零,因此不会执行if语句段,而执行后面的代码,即输出printf2:x=0.因此本程序输出结果为(子进程与父进程顺序不唯一)

printf2:x=0
printf1:x=2
printf2:x=1

fork()函数示例四

    

#include "csapp.h"
#define N 3
int main()
{
  int status,i;
  pid_t pid; 

  for(i=0;i<N;i++)
    if((pid=fork())==0) //新建子进程
      exit(100+i); 

  while((pid=waitpid(-1,&status,0))>0) {  //如果子进程是正常终止的,就返回进程的进程号PID
    if(WIFEXITED(status))  //返回退出状态
      printf("child %d terminated normally with exit status =%d\n",pid,WEXITSTATUS(status));
    else
      printf("child %d erminated abnormally\n",pid);
  }

  if(errno!=ECHILD)
    printf("waitpid error\n");
  exit(0);
}

    本代码主要是测试进程的终止,即waitpid案例程序。定义生成两个进程,本例子是不按照特定顺序来回收僵死子进程,本程序返回结果为

child 28693 terminated normally with exit status =100
child 28694 terminated normally with exit status =101
child 28695 terminated normally with exit status =102

fork()函数示例五

    

#include "csapp.h"
#define N 2
int main()
{
  int status,i;
  pid_t pid[N],retpid;

  for(i=0;i<N;i++)
    if((pid[i]=fork())==0)
      exit(100+i);//退出并返回状态码
  i=0;
  while((retpid=waitpid(pid[i++],&status,0))>0) {
    if(WIFEXITED(status))
      printf("child %d terminated normally with exit status=%d\n",retpid,WEXITSTATUS(status));
    else
      printf("child %d terminated abnormally\n",retpid);
  }

  if(errno !=ECHILD)
    printf("waitpid error!");
  exit(0);
}

    按照创建进程的顺序来回收这些僵死进程,注意程序中的pid[i++]是按序的标志,本程序运行结果为

child 29846 terminated normally with exit status=100
child 29847 terminated normally with exit status=101

fork()函数示例六

#include "csapp.h"

/*推测此程序会输出什么样的结果*/

int main()
{
  int status;
  pid_t pid;

  printf("Hello\n");
  pid=fork();
  printf("%d\n",!pid);
  if(pid!=0) {
    if(waitpid(-1,&status,0)>0) {
      if(WIFEXITED(status)!=0)
    printf("%d\n",WEXITSTATUS(status));
    }
  }
  printf("Bye\n");
  exit(2);
}

    首先父进程会输出Hello,子进程新建成功,在子进程中,pid为0,输出1,并且子进程无法执行if语句,但是子进程仍然可以输出Bye,并且正常退出并返回状态码为2。而在父进程中,pid为非零的正数,因此先输出0,然后执行if语句,由于子进程已经正常退出,故输出状态码2,并且最后执行公共代码块,输出Bye并正常退出。因此,总共输出的结果如下所示:

Hello
0
1
Bye
2
Bye

当然,顺序不唯一,还有一种可能的结果是

Hello
1
Bye
0
2
Bye

转载于:https://my.oschina.net/zzw922cn/blog/418797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值