do_fork() 源码剖析

本文详细解析了do_fork()函数的主要步骤,包括进程描述符复制、进程初始化及写时拷贝机制等关键环节,并阐述了fork()系统调用的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  分析一下do_fork()的源码的主要步骤

1.首次查找_pidmap位图,为新子进程分配新的pid

2.复制进程描述符,返回的是新的进程描述符的地址(struct task_struct *p)

3.初始化完成量,vfrok主要用excv,父进程的虚拟地址空间对其没有用处,所以实现方式为在子进程退出或者替换后父进程才开始执行

 

4.如果设置了vfork,则调用wait(父进程)

5.free_pidmap(pid):

6.返回子进程pid。

 

对于第二步,是do_fork的关键

1.检查flag位的合法性

2.为子进程获取进程描述符

  开辟内核栈+thread_info 一般大小为8k

  将current的值赋给子进程

 

  

3.检查线程数量,设置一些关键字,保存新的pid

4.用系统调用时cpu寄存器中的值初始化新线程,将exa置为0(fork和clone在子进程的返回值)

 

 5.完成一些字段的设置,将新进程加入到链表,将新进程pid加入到散列表

 

复制父进程每一个vm_area_struct,也复制它的页表,将私有的可写的页都标记为只读,为写时拷贝做准备。

 

 

 1.判断是否为创建线程,如果是线程,直接使用mm = oldmm ,表示线程公用虚拟地址空间

2.对于非线程,为其创建虚拟地址空间,创建新的局部描述符加入到tsk地址空间,之后调用dup_mmap;

 

后续需要解决的问题:

1.current是什么

task_struct 包含了进程所有的信息,current是一个宏,由getCurrent()->task替换,此函数内部是一条汇编指令,在x86体系下通过在内核栈尾部插入thread_info结构,计算偏移量,来查找到当前正在运行的进程描述符。

2.用户态fork()->内核态sys_fork()的过程是什么

普通程序调用fork()-->库函数fork()-->系统调用(fork功能号)-->由功能号在 sys_call_table[]中寻到sys_fork()函数地址-->调用sys_fork(),这就完成拉从用户态到内核态的变化过程。

 

总结一下:

  看源码前对fork()只停留在用上,看了之后明白了很多死记硬背的点,也有了自己的理解。主要几个点,fork的返回值,进程和线程的区别,vfork现阶段还暂时没有用过先记住吧,还包括子进程继承父进程的信息,还有写时拷贝的先决条件等。
 

转载于:https://www.cnblogs.com/zhangtiezi/p/8372080.html

下面是 `sched_post_fork` 函数的部分源码,代码摘自 Linux 内核版本 5.15.5: ```c void sched_post_fork(struct task_struct *p) { struct rq_flags rf; struct sched_entity *se = &p->se; struct task_struct *parent = p->parent; struct sched_entity *parent_se = &parent->se; if (parent_se->on_rq && !se->on_rq) { /* * parent is on a runqueue, but we just forked the child and it's * not on any runqueue yet. This means we need to do a few things * to get the child properly accounted for. */ p->se.load.weight = 0; se->vruntime = parent_se->vruntime; se->sum_exec_runtime = parent_se->sum_exec_runtime; se->prev_sum_exec_runtime = parent_se->prev_sum_exec_runtime; se->avg_overlap = parent_se->avg_overlap; /* * We need to add the child to the runqueue. This is tricky, as * we cannot just add it to the parent's runqueue, as that would * mess up the order of the tasks. Instead, we need to add it * to the right runqueue based on its priority. */ raw_spin_lock_irqsave(&rq_lock(p), rf); enqueue_task_rq(p, task_cpu(p), ENQUEUE_WAKEUP); raw_spin_unlock_irqrestore(&rq_lock(p), rf); } /* * We need to reset the child's CPU time and other accounting * information, as it is starting fresh. */ schedstat_set(p->se.statistics.wait_start, 0); cpuacct_clear_stats(p); memset(&p->sched_info, 0, sizeof(p->sched_info)); } ``` 该函数主要做了以下几件事情: 1. 复制父进程的调度实体信息,包括进程的优先级、调度策略、调度参数等; 2. 为子进程创建新的调度实体,并将其加入到任务队列中; 3. 重置子进程的 CPU 时间和其他计算信息,以确保子进程可以从头开始执行。 需要注意的是,`sched_post_fork` 函数只是为子进程更新调度信息和创建调度实体,并将其加入到任务队列中。具体的调度过程和调度策略等信息,是由 CFS 调度器来进行实现和维护的。因此,在理解 `sched_post_fork` 函数的实现时,需要结合 CFS 调度器的内部实现和调度策略等信息进行理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值