千家信息网

linux中的fork函数是如何创建一个新的进程的

发表于:2025-11-09 作者:千家信息网编辑
千家信息网最后更新 2025年11月09日,本篇文章为大家展示了linux中的fork函数是如何创建一个新的进程的,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。前面已经谈了内核加载与系统引导过程,下面我
千家信息网最后更新 2025年11月09日linux中的fork函数是如何创建一个新的进程的

本篇文章为大家展示了linux中的fork函数是如何创建一个新的进程的,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

前面已经谈了内核加载与系统引导过程,下面我们来看看内核的 do_fork() 函数是如何创建一个新的进程的。


在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是 sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数 do_fork() 来实现的。根据


调用时所使用的 clone_flags 参数不同,do_fork() 函数完成的工作也各异。


这部分内容简单,我不打算就此而展开分析。下面我们重点来讲解以下 do_fork() 函数的工作原理。


我们知道 do_fork() 函数生成一个新的进程,大致分为三个步骤。


1、建立进程控制结构并赋初值,使其成为进程映像。这个过程完成以下内容。



    在内存中分配一个 task_struct 数据结构,以代表即将产生的新进程。

    把父进程 PCB 的内容复制到新进程的 PCB 中。

    为新进程分配一个唯一的进程标识号 PID 和 user_struct 结构。然后检查用户具有执行一个新进程所必须具有的资源。

    重新设置 task_struct 结构中那些与父进程值不同的数据成员。

    设置进程管理信息,根据所提供的 clone_flags 参数值,决定是否对父进程 task_struct 中的指针 fs 、files 指针等所选择的部分进行拷贝,如果 clone_flags 参数指明的是


    共享而不是拷贝,则将其计数器 count 的值加 1 ,否则就拷贝新进程所需要的相关信息内容 PCB 。这个地方是区分 sys_fork() 还是 sys_clone() 。

2、必须为新进程的执行设置跟踪进程执行情况的相关内核数据结构。包括 任务数组、自由时间列表 tarray_freelist 以及 pidhash[] 数组。

这部分完成如下内容:



    把新进程加入到进程链表中。

    把新进程加入到 pidhash 散列表中,并增加任务计数值。

    通过拷贝父进程的上、下文来初始化硬件的上下文(TSS段、LDT以及 GDT)。

3、启动调度程序,使子进程获得运行的机会。

这部分完成以下动作:



    设置新的就绪队列状态 TASK_RUNING , 并将新进程挂到就绪队列中,并重新启动调度程序使其运行。

    向父进程返回子进程的 PID,设置子进程从 do_fork() 返回 0 值。

下面就具体的 do_fork() 函数程序代码进行分析(该代码位于 kernel/fork.c 文件中)



int do_fork(unsigned long clone_flags,unsigned long stack_start, struct pt_regs *regs,

unsigned long stack_size)

{

int retval;

struct task_struct *p;

struct completion vfork;


retval = -EPERM ;


if ( clone_flags & CLONE_PID )

{

if ( current->pid )

goto fork_out;

}


reval = -ENOMEM ;



p = alloc_task_struct(); // 分配内存建立新进程的 task_struct 结构

if ( !p )

goto fork_out;


*p = *current ; //将当前进程的 task_struct 结构的内容复制给新进程的 PCB结构


retval = -EAGAIN;


//下面代码对父、子进程 task_struct 结构中不同值的数据成员进行赋值


if ( atomic_read ( &p->user->processes ) >= p->rlim[RLIMIT_NPROC].rlim_cur

&& !capable( CAP_SYS_ADMIN ) && !capable( CAP_SYS_RESOURCE ))

goto bad_fork_free;


atomic_inc ( &p->user->__count); //count 计数器加 1

atomic_inc ( &p->user->processes); //进程数加 1


if ( nr_threads >= max_threads )

goto bad_fork_cleanup_count ;


get_exec_domain( p->exec_domain );


if ( p->binfmt && p->binfmt->module )

__MOD_INC_USE_COUNT( p->binfmt->module ); //可执行文件 binfmt 结构共享计数 + 1

p->did_exec = 0 ; //进程未执行

p->swappable = 0 ; //进程不可换出

p->state = TASK_UNINTERRUPTIBLE ; //置进程状态

copy_flags( clone_flags,p ); //拷贝进程标志位

p->pid = get_pid( clone_flags ); //为新进程分配进程标志号

p->run_list.next = NULL ;

p->run_list.prev = NULL ;

p->run_list.cptr = NULL ;


init_waitqueue_head( &p->wait_childexit ); //初始化 wait_childexit 队列


p->vfork_done = NULL ;


if ( clone_flags & CLONE_VFORK ) {

p->vfork_done = &vfork ;

init_completion(&vfork) ;

}


spin_lock_init( &p->alloc_lock );


p->sigpending = 0 ;


init_sigpending( &p->pending );

p->it_real_value = p->it_virt_value = p->it_prof_value = 0 ; //初始化时间数据成员

p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0 ; //初始化定时器结构

init_timer( &p->real_timer );

p->real_timer.data = (unsigned long)p;

p->leader = 0 ;

p->tty_old_pgrp = 0 ;

p->times.tms_utime = p->times.tms_stime = 0 ; //初始化进程的各种运行时间

p->times.tms_cutime = p->times.tms_cstime = 0 ;

#ifdef CONFIG_SMP //初始化对称处理器成员

{

int i;

p->cpus_runnable = ~0UL;

p->processor = current->processor ;

for( i = 0 ; i < smp_num_cpus ; i++ )

p->per_cpu_utime[ i ] = p->per_cpu_stime[ i ] = 0;

spin_lock_init ( &p->sigmask_lock );

}


#endif

p->lock_depth = -1 ; // 注意:这里 -1 代表 no ,表示在上下文切换时,内核不上锁

p->start_time = jiffies ; // 设置进程的起始时间


INIT_LIST_HEAD ( &p->local_pages );

retval = -ENOMEM ;


if ( copy_files ( clone_flags , p )) //拷贝父进程的 files 指针,共享父进程已打开的文件

goto bad_fork_cleanup ;


if ( copy_fs ( clone_flags , p )) //拷贝父进程的 fs 指针,共享父进程文件系统

goto bad_fork_cleanup_files ;


if ( copy_sighand ( clone_flags , p )) //子进程共享父进程的信号处理函数指针

goto bad_fork_cleanup_fs ;


if ( copy_mm ( clone_flags , p ))

goto bad_fork_cleanup_mm ; //拷贝父进程的 mm 信息,共享存储管理信息


retval = copy_thread( 0 , clone_flags , stack_start, stack_size , p regs );

//初始化 TSS、LDT以及GDT项


if ( retval )

goto bad_fork_cleanup_mm ;


p->semundo = NULL ; //初始化信号量成员


p->prent_exec_id = p-self_exec_id ;


p->swappable = 1 ; //进程占用的内存页面可换出


p->exit_signal = clone_flag & CSIGNAL ;


p->pdeatch_signal = 0 ; //注意:这里是父进程消亡后发送的信号


p->counter = (current->counter + 1) >> 1 ;//进程动态优先级,这里设置成父进程的一半,应注意的是,这里是采用位操作来实现的。


current->counter >> =1;


if ( !current->counter )

current->need_resched = 1 ; //置位重新调度标记,实际上从这个地方开始,分裂成了父子两个进程。



retval = p->pid ;


p->tpid = retval ;

INIT_LIST_HEAD( &p->thread_group );


write_lock_irq( &tasklist_lock );


p->p_opptr = current->p_opptr ;

p->p_pptr = current->p_pptr ;


if ( !( clone_flags & (CLONE_PARENT | CLONE_THREAD ))) {

p->opptr = current ;

if ( !(p->ptrace & PT_PTRACED) )

p->p_pptr = current ;

}


if ( clone_flags & CLONE_THREAD ){

p->tpid = current->tpid ;

list_add ( &p->thread_group,¤t->thread_group );

}


SET_LINKS(p);


hash_pid(p);

nr_threads++;


write_unlock_irq( &tasklist_lock );

if ( p->ptrace & PT_PTRACED )

send_sig( SIGSTOP , p ,1 );

wake_up_process(p); //把新进程加入运行队列,并启动调度程序重新调度,使新进程获得运行机会

++total_forks ;

if ( clone_flags & CLONE_VFRK )

wait_for_completion(&vfork);


//以下是出错处理部分

fork_out:

return retval;

bad_fork_cleanup_mm:

exit_mm(p);

bad_fork_cleanup_sighand:

exit_sighand(p);

bad_fork_cleanup_fs:

exit_fs(p);

bad_fork_cleanup_files:

exit_files(p);


bad_fork_cleanup:

put_exec_domain( p->exec_domain );


if ( p->binfmt && p->binfmt->module )

__MOD_DEC_USE_COUNT( p->binfmt->module );

bad_fork_cleanup_count:

atomic_dec( &p->user->processes );

free_uid ( p->user );

bad_fork_free:

free_task_struct(p);

goto fork_out;

}




PS:

代码是分析完了,有两个方面的体会:

一、这个函数重点是理解进程分裂的部分,其中两次返回 pid 的值是理解的重中之重。

二、尽管我一直不主张在程序中大量使用 goto 语句,不得不承认,那些大牛的 goto 语句用在此处是恰到好处啊。^_^

上述内容就是linux中的fork函数是如何创建一个新的进程的,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。

进程 函数 结构 内容 拷贝 内核 成员 指针 数据 程序 调度 运行 代码 信息 文件 时间 队列 不同 信号 内存 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 美国太空总数气象数据库 更改数据库表类型 服务器10m带宽可以容纳多少人 数据库管理技术关系的性质 国产信创服务器多少钱 网络安全事故警示 计算机网络技术教材推荐 网络安全的关键是网络中的 施乐2022服务器地址未登记 格力电器软件开发待遇怎么样 东莞市君为网络技术有限公司 微信支付出现服务器错误 fifaonline3服务器连接异常 黑龙江医疗管理软件开发公司 北京北斗网络技术有限公司 网络技术服务提供者的概念 软件开发毕设周志 赛尔号怎么看服务器在线玩家 win10 怎么卸载数据库 部队观看网络安全教育片讨论交流 数据库与数据仓库导论 未来互联网大会领先科技 怎么查看连接服务器里的网址 宝山区项目数据库服务收费标准 上海网络技术转让价格表格 网络安全教育知识竞答答案 安信交易服务器慢 cvs怎么导入mysql数据库 软件开发 太原分公司 深圳办公系统软件开发有用吗
0