趣谈 Linux 操作系统
刘超
前网易杭州研究院云计算技术部首席架构师
85458 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 72 讲
趣谈 Linux 操作系统
15
15
1.0x
00:00/00:00
登录|注册

18 | 进程的创建:如何发起一个新项目?

调用__enqueue_entity
调用update_curr
调用resched_curr
调用wakeup_preempt_entity
调用update_curr
调用enqueue_entity
调用相应的check_preempt_wakeup
调用相应的enqueue_task_fair
copy_fs_struct
dup_fd
调用check_preempt_curr
调用enqueue_task
设置进程状态为TASK_RUNNING
dup_mmap
dup_mm
copy_signal
copy_sighand
init_sigpending
copy_fs
copy_files
调用调度类的task_fork函数
设置调度类
初始化优先级
__sched_fork
prepare_creds
setup_thread_stack
arch_dup_task_struct
alloc_thread_stack_node
alloc_task_struct_node
唤醒新进程
分配pid,设置tid,group_leader,并且建立进程之间的亲缘关系
复制进程内存空间
初始化与信号相关的变量
初始化与文件和文件系统相关的变量
设置调度相关变量
设置进程运行统计量
copy_creds
copy_process
_do_fork
sys_fork
fork系统调用
进程的创建

该思维导图由 AI 生成,仅供参考

前面我们学习了如何使用 fork 创建进程,也学习了进程管理和调度的相关数据结构。这一节,我们就来看一看,创建进程这个动作在内核里都做了什么事情。
fork 是一个系统调用,根据咱们讲过的系统调用的流程,流程的最后会在 sys_call_table 中找到相应的系统调用 sys_fork。
sys_fork 是如何定义的呢?根据 SYSCALL_DEFINE0 这个宏的定义,下面这段代码就定义了 sys_fork。
SYSCALL_DEFINE0(fork)
{
......
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}
sys_fork 会调用 _do_fork。
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
int trace = 0;
long nr;
......
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
......
if (!IS_ERR(p)) {
struct pid *pid;
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
......
wake_up_new_task(p);
......
put_pid(pid);
}
......

fork 的第一件大事:复制结构

_do_fork 里面做的第一件大事就是 copy_process,咱们前面讲过这个思想。如果所有数据结构都从头创建一份太麻烦了,还不如使用惯用“伎俩”,Ctrl C + Ctrl V。
这里我们再把 task_struct 的结构图拿出来,对比着看如何一个个复制。
static __latent_entropy struct task_struct *copy_process(
unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace,
unsigned long tls,
int node)
{
int retval;
struct task_struct *p;
......
p = dup_task_struct(current, node);
dup_task_struct 主要做了下面几件事情:
调用 alloc_task_struct_node 分配一个 task_struct 结构;
调用 alloc_thread_stack_node 来创建内核栈,这里面调用 __vmalloc_node_range 分配一个连续的 THREAD_SIZE 的内存空间,赋值给 task_struct 的 void *stack 成员变量;
调用 arch_dup_task_struct(struct task_struct *dst, struct task_struct *src),将 task_struct 进行复制,其实就是调用 memcpy;
调用 setup_thread_stack 设置 thread_info。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

在内核中创建进程是一个复杂的操作,本文详细介绍了这一过程。通过系统调用fork来发起一个新的进程,然后在内核中执行sys_fork系统调用,最终调用_do_fork函数来完成进程的创建。_do_fork函数首先复制task_struct结构,然后处理权限、调度、文件和文件系统、信号以及进程内存空间等相关变量。文章详细介绍了每个步骤的具体实现细节,包括复制task_struct结构、处理权限、初始化统计量、设置调度相关变量、复制文件和文件系统信息、初始化信号相关变量、复制进程内存空间等。整个过程展现了在内核中创建进程所涉及的复杂操作和技术细节。读者可以通过本文了解到在内核中创建进程的具体步骤和技术细节,为深入理解内核操作提供了重要参考。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《趣谈 Linux 操作系统》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(37)

  • 最新
  • 精选
  • 刘強
    文章中出现了SYSCALL_DEFINE0宏定义,不明白,就网上查了一下,一看吓一跳,宏定义里面又有一堆宏定义,其实就是一个函数调用,为什么弄得这么复杂呢?原来是为了修复一个bug。这让我意识到linux内核代码的复杂性。linux是一个集大成者,为了适应各种硬件架构平台,修复各种意想不到的bug,里面充斥着各种兼容性代码,修复补丁等等。而且里面的代码也是世界各路大神,黑客写出来的,为了保证内核的安全性,健壮性,扩展性,考虑的东西非常之多,充斥着各种奇技淫巧,不是我等普通人短时间能够理解。每一行代码,甚至一个宏定义,都是要花时间研究的。从这个角度上来说,linux就像是一个迷宫,如果没有一个向导,进去后估计就出不来了。也许这个专栏的作用就是充当一个向导,欣赏沿途风景的同时,带领我们穿越迷宫,找到出口...

    作者回复: 是的。

    2019-05-08
    44
  • 刘強
    有个问题: 在数据库中,有个事务的概念,也就是保证一连串操作的原子性,如果其中任何一步错误,整个操作回滚,回到原来的状态,好像什么也没发生。但是在文章中我看到,在创建进程的过程中,步骤太多了。每一步都要申请空间,复制数据。如果其中一步发生了错误,怎么保证释放这些空间,回到原来状态?

    作者回复: 错了会做错误处理的,没有啥捷径,都是代码里面自己做的,写c就要这样,每一步都要清楚自己创建了什么,万一错误应该销毁什么。如果程序员不做这个,没有人帮忙,不像java还有个gc

    2019-05-08
    3
    20
  • Milittle
    老师,要是能把对应代码路径给出就好了,有时候自己找不见,谢谢老师~

    作者回复: 其实不用纠结,因为代码过一阵就变了,关键是理解原理和流程。我原来做过代码逐行分析的这种,但是发现这种文章过一阵就没法看了。

    2019-05-10
    2
    17
  • zhengfan
    刘老师: 遇到一个问题。 您在上面两个章节提到过“进程调度第一定律”,是说任何被调度的task(无论是获得还是交出运行权一方)都是在调用__schedule方法,并因此在进程实际切换完成后不需修改指令指针寄存器。 以此来思考本节介绍的创建进程过程。父进程在交出运行权的时候没什么特殊的,一定是在执行__schedule方法。 然而当子进程获得运行权的时候,因为之前它没运行过,不是通过__schedule方法交出运行权的,当前的指令指针寄存器和子进程运行状态的上下文(dup_task_struct中?)不相符吧?这样不会出问题吗?

    作者回复: 不会的,调度这个函数是在内核里面的,这个逻辑其实不属于任何一个进程,反而是上下文的数据才标志了属于哪个进程。而且子进程刚fork完之后,没有exec之前,所有都和父进程一模一样

    2020-04-17
    8
  • 注意力$
    超哥,Oracle 这种多进程的数据库,和mysql 这种单进程多线程的数据库,在进程管理上有什么优势呢?看见创建进程这么复杂,资源消耗也多

    作者回复: 创建复杂,创建起来就好了,很多多进程的软件,也不是任务来了,现创建进程的,而是事先创建好了,等待分配任务。

    2020-05-30
    6
  • 尚墨
    反复研读都已经高亮了。我几乎每篇都要听,读三次以上,才能懵懵懂懂。

    作者回复: 再难的知识就怕反复研究,加油

    2019-05-11
    5
  • 一苇渡江
    老师写的太棒了,特别是这个图,肯定是花了不少时间,把这个图手抄了一遍,时不时拿出来看看

    作者回复: 手抄啊,牛

    2019-05-08
    5
  • 蚂蚁内推+v
    内核态的内核进程和用户态的用户进程创建过程有区别吗?

    作者回复: 有区别的

    2019-05-24
    3
    2
  • 安排
    调度类是全局的吗?还是每个cpu核有自己的调度类集合?

    作者回复: 类是全局的。里面主要实现的是算法。

    2019-05-08
    2
    1
  • 周平
    讲得好的细节,与前面的内容可以无缝连接,不至于管中窥豹,让学习者越学越乱,谢谢老师

    作者回复: 赞

    2019-05-17
收起评论
显示
设置
留言
37
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部