Linux下的 fork 函数

之前只是了解到linux中的fork函数是用来创建进程,并没有太多的去学习,这里学习记录如下。

撰写不易,转载需注明出处:http://blog.csdn.net/jscese/article/details/44401389 本文来自 【jscese】的博客!

定义:

来自百科的解释:fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

通俗的来理解,就是程序跑到fork的时候,进行了一次分身,然后两个基本基本相同的家伙都会独立的接着往下跑.

原型:

pid_t fork( void);

返回值:

这里的pid_t 是一个宏,由一系列的types.h文件定义后,实际是int类型

而一次调用fork函数是会有两次返回,一次是调用进程的返回,一次是新建进程的返回.

上面说到调用到fork的时候,在fork当中就会进行“分身”,资源状态的复制,所以创建的新进程也是停留在fork函数当中的,调用fork函数的进程可以称做父进程,新建的为子进程.

fork返回值:

父进程就会返回创建的子进程的进程ID.

子进程刚刚被创建,并没有子进程,所以直接返回 0.

如果出错返回负数 .

fork出错可能有两种原因:

1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。

2)系统内存不足,这时errno的值被设置为ENOMEM。

实现:

而fork函数的定义是在/usr/include/unistd.h中有定义,实际调用时是通过系统调用.

在kernel源码中的unistd.h中可以找到这样的宏定义:

#define __NR_fork 1079
#ifdef CONFIG_MMU
__SYSCALL(__NR_fork, sys_fork)
#else
__SYSCALL(__NR_fork, sys_ni_syscall)
#endif /* CONFIG_MMU */

继续跟进systemcall的实现可以看syscalls.h以及sys_arm.c,分别看下sys_fork的定义和实现:

/* Fork a new task - this creates a new program thread.
 * This is called indirectly via a small wrapper
 */
asmlinkage int sys_fork(struct pt_regs *regs)
{
#ifdef CONFIG_MMU
    return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
#else
    /* can not support in nommu mode */
    return(-EINVAL);
#endif
}

可以看到实际上是调用了一个 do_fork 的函数.这才是fork 被系统调用进的接口.

继续看下这个do_fork函数,实现是在kernel源码下的/kernel/fork.c(3.1.10):

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
struct task_struct *p;   // 而这个task_struct结构体即是Linux中对一个进程的描述符
...

    p = copy_process(clone_flags, stack_start, regs, stack_size,
             child_tidptr, NULL, trace);//这里进行copy复制 代码段,数据段,BSS段,堆,栈等所有用户空间的信息

}

上面只是简单的追踪了一下fork的实现,特别是 系统调用 以及 进程资源复制 都是比较复杂的,我接触不多,这里暂时放下,后续有机会去学习.

使用:

作为函数使用,自然离不开代码,下面附上我自己写的一个小测试:

#include <stdio.h>
#include <unistd.h>

int main(int argc,char* argv[])
{

printf("begain parent process pid ==%x \n",getpid());

pid_t ipid1=fork();

if(ipid1==0)
{
 printf("first child process pid==%x ,parent process pid==%x \n",getpid(),getppid());

}else
{

printf("first fork child process pid==%x ,parent process pid==%x\n",ipid1,getpid());

}

pid_t ipid2=fork();

if(ipid2==0)
{
  printf("second child process pid==%x ,parent process pid == %x \n",getpid(),getppid());

}else
{

printf("second fork child process pid==%x ,parent process pid==%x\n",ipid2,getpid());
}

printf("=========================================  \n");
sleep(1);

}

gcc编译运行之后结果如下:

begain parent process pid ==36fd
first fork child process pid==36fe ,parent process pid==36fd
first child process pid==36fe ,parent process pid==36fd
second fork child process pid==36ff ,parent process pid==36fd
=========================================
second fork child process pid==3700 ,parent process pid==36fe
=========================================
second child process pid==36ff ,parent process pid == 36fd
=========================================
second child process pid==3700 ,parent process pid == 36fe
=========================================

可以看到最开始main进程的PID是 36fd ,第一次 fork之后,上面有说到是会返回两个值的,并且都会继续往下执行代码段(两份相同的代码,并不共享数据).

其中36fd(父) 进程返回的是子进程的PID,所以 ipid1 肯定不会等于 0,所以运行:

first fork child process pid==36fe ,parent process pid==36fd

显然返回的子进程PID 为36fe.

(getpid()获取当前进程PID,getppid()获取父进程PID)

而上面创建的这个36fe的子进程返回的必然就是 0 了,往下执行 ipid 就等于 0,所以运行:

first child process pid==36fe ,parent process pid==36fd 

我这里是父进程先跑出fork往下运行,所以先打印了first fork,这个应该是跟进程调度策略有关,我是直接在ubuntu上运行.

代码继续往下,这两个进程(36fd(父),36fe(子)) 都会再进入第二个fork函数.

分别又会像上面分析到的那样创建一个新的进程分别是 36ff 3670 ,运行模式同上分析。

最后如果不加个sleep(1);

最后两行的打印结果会是:

second child process pid==36ff ,parent process pid == 1
=========================================
second child process pid==3700 ,parent process pid == 1
=========================================  

因为等最新的两个新进程开跑的时候, 前面最开始的那两个老进程跑完了~getppid()返回 1

至于总共有多少个进程,可以看分割线 ===== 条数 ,因为每个进程都会跑最后的共用代码.

我初步的理解分析就到这里吧~ 后续有机会深入….

时间: 2024-10-19 15:44:06

Linux下的 fork 函数的相关文章

关于linux下的fork()函数

上题 1. int main( ){ int i; for(i = 0; i<5;i++){ int pid = fork(); if(pid == 0){ //do something } else { //do something } } // do somthing,sleep return 0;}5101532 2 2.Linux下多少个"-"将被打印: int main(void){ int i; for(i=0;i<4;i++){ fork(); printf(

linux下的fork和execve函数使用

fork函数是linux中创建进程的函数,linux创建进程只有用fork,别无他法.我自己写代码fork用的不多,对它的一些细节还不是清楚,今天抽空研究了下fork,把它的一些关键点总结一下,以后用到了自己也好有个参考. 1)fork函数会在父进程中创建子进程,子进程的堆,栈,数据段,PC指针都是从父进程中复制过来的,和父进程是独立的,但是内容是一致的.代码段子进程和父进程是共享的. 2)fork()的返回值可能为-1,0,和一个正数.-1表示fork()调用失败,0表示返回子进程执行结果,正

Linux下使用system()函数一定要谨慎 【转载】

出处:http://blog.csdn.net/kyokowl/article/details/8823334 C/C++]Linux下使用system()函数一定要谨慎 曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点.当初因为这个函数风险较多,故抛弃不用,改用其他的方法.这里先不说我用了什么方法,这里必须要搞懂syste

linux下转格式函数iconv段错误

今天将windows代码移植到Linux下,其中用到了Unicode转char的函数,被坑了一会,相关函数及编码格式,Linux与windows不同,有几点需要注意: 1.wchar_t 在Linux下占用4个字节,在windows下占2个字节: 2.Linux默认的文本编码方式是UTF-8:Linux终端汉字显示的设置方式:vi /etc/sysconfig/i18n: 设置LANG="en_US.UTF-8"或者LANG="zh_CN.UTF-8": 3.ico

linux下的信号处理函数总结

1.信号处理函数 相关函数原型如下: #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 第一参数是信号 第二个参数是信号处理器:             1.可以是SIG_DFL,信号的默认动作             2. 可以是SIG_IGN,忽略该信号             3. 一个带有一个整型参数的处理函数. #include <signal.h> int sigacti

linux下C语言函数执行时间统计

转载:http://blog.csdn.net/linquidx/article/details/5916701#t5 写好程序,用gcc编译,带上-pg参数,然后运行以后分析gmon.out文件: 命令exp:   gprof ./test-main ./gmon.out >1.log  在1.log中会生成各函数运行情况. gprof 1.1 简介 gprof实际上只是一个用于读取profile结果文件的工具.gprof采用混合方法来收集程序的统计信息,他使用检测方法,在编译过程中在函数入口

Linux下利用ioctl函数获取网卡信息

linux下的ioctl函数原型如下: #include <sys/ioctl.h> int ioctl(int handle, int cmd, [int *argc, int argv]) 函数成功返回0,失败返回-1. 其相关命令接口如下: 类别 Request 说明 数据类型 套 接 口 SIOCATMARK SIOCSPGRP SIOCGPGRP 是否位于带外标记 设置套接口的进程ID 或进程组ID 获取套接口的进程ID 或进程组ID int int int 文 件 FIONBIO

Linux下利用signal函数处理ctrl+c等信号

前言 linux下可以通过信号机制来实现程序的软中断,是一个非常有用的编程方法.我们平时在程序运行的时候按下ctrl-c.ctrl-z或者kill一个进程的时候其实都等效于向这个进程发送了一个特定信号,当进程捕获到信号后,进程会被中断并立即跳转到信号处理函数.默认情况下一个程序对ctrl-c发出的信号(SIGINT)的处理方式是退出进程,所以当我们按下ctrl-c的时候就可以终止一个进程的运行. signal函数 但是有时候我们希望我们的程序在被信号终止之前执行一些特定的收尾流程,或者我们希望我

linux下实现ls()函数遍历目录

需求:在linux下遍历目录,输出目录中各文件名. 在linux下遍历目录的相关函数有: #include <dirent.h> DIR* opendir(const char* dir_path); struct dirent* readdir(DIR* dirp); int closedir(DIR* dirp); int lstat(const chat* filename,struct stat* st); 在这里涉及到几个结构体:DIR,struct dirent,struct st