热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

使用C语言的fork()函数在Linux中创建进程的实例讲解

这篇文章主要介绍了使用C语言的fork()函数在Linux中创建进程的实例讲解,fork在父进程下创建出的子进程可以与父进程一起来多进程运行同一个程序,需要的朋友可以参考下

在Linux中创建一个新进程的唯一方法是使用fork()函数。fork()函数是Linux中一个非常重要的函数,和以往遇到的函数有一些区别,因为fork()函数看起来执行一次却返回两个值。

fork()函数用于从已存在的进程中创建一个新进程。新进程称为子进程,而园进程称为父进程。使用fork()函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程的上下文、代码段、进程堆栈、内存信息、打开的文件描述符、符号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。

因为子进程几乎是父进程的完全复制,所以父子两进程会运行同一个程序。这就需要用一种方式来区分它们,并使它们照此运行,否则,这两个进程不可能做不同的事。实际上是在父进程中执行fork()函数时,父进程会复制一个子进程,而且父子进程的代码从fork()函数的返回开始分别在两个地址空间中同时运行,从而使两个进程分别获得所属fork()函数的返回值,其中在父进程中的返回值是子进程的进程号,而在子进程中返回0。因此,可以通过返回值来判断该进程的父进程还是子进程。

同时可以看出,使用fork()函数的代价是很大的,它复制了父进程中的代码段、数据段和堆栈段里的大部分内容,使得fork()函数的系统开销比较大,而且执行速度也不是很快。

fork()函数语法

2016622171656749.jpg (495×165)

fork()函数出错可能有两种原因:
1、当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN
2、系统内存不足,这时errno的值被设置为ENOMEM

示例

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

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


/* 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;i0) { //如果子进程是正常终止的,就返回进程的进程号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;i0) {
  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


推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • 树莓派Linux基础(一):查看文件系统的命令行操作
    本文介绍了在树莓派上通过SSH服务使用命令行查看文件系统的操作,包括cd命令用于变更目录、pwd命令用于显示当前目录位置、ls命令用于显示文件和目录列表。详细讲解了这些命令的使用方法和注意事项。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
author-avatar
台州路桥古筝音乐家教
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有