Linux的fork()系统调用,就是以父进程为模版创建子进程,是Linux系统的进程管理机制的核心API之一,另一个是调度器函数schedule(),它的用户态API就是之前说自旋锁时提到的sched_yield()。

如果是“21天学写操作系统”,那么最先要实现的就是这两个API。实现了这两个就可以在bochs虚拟机上跑个多进程demo了。

先看fork的man手册介绍:

就是创建子进程,并且返回pid进程号。它的特点是fork一次返回两次,子进程返回0,父进程返回子进程的pid。

Linux有getpid()、getppid()函数,子进程可以使用后者获得父进程的进程号,但父进程可能有多个子进程,所以用fork返回值表示最近创建的子进程号。

它的常见用法就是:

pid_t cpid = fork();

if (-1 == cpid) {

//fork失败,一般是内存不够了

} else if (0 == cpid) {

//子进程代码

} else {

//父进程代码

}

类似的代码在很多介绍fork的书上出现,代码流程在fork之后根据它的返回值是否为0分成了两个分支,就像娜迦海妖的叉状闪电(forked lightning)一样,程序在这里分成了两个分支:(

太极生两仪,这是属于Linus大神的黑魔法之一,我们接下来窥探一下。

如上图,首先要定义一个task_t的结构来代表“进程”,我们这种示例代码只需要设置3个值就行,“进程号”id,栈指针rsp,指令指针rip。task 的成员变量与x64同名寄存器一样,用来在“进程切换”时保存这两个关键信息。

我们只支持2个进程,进程的task_t大小为4096字节,一个内存页。它的低地址是task_t数据结构,高地址是“进程的栈”。

t_fork()函数的前半部分是获取一个task_t结构,然后用memcpy()函数把父进程的task_t拷贝到子进程,完成“子进程的复制”。

子进程与父进程不同的只有3项,即id,栈指针rsp,启动地址rip。

子进程是初次执行,模拟从fork()调用中返回,所以它的启动地址rip设置为do_fork_first汇编函数。

然后设置它的栈指针rsp,这个需要计算,方法就是把当前的栈指针(隶属于父进程)在父进程的task_t里的偏移量,加在子进程里就行。

子进程是父进程的拷贝,除了task_t不一样,数据部分是一样的。

算法:child>rsp = child + parent->rsp - parent,当前的rsp寄存器的值就是父进程的栈指针。

因为要获取寄存器rsp的值,这个计算也放在do_fork()汇编函数里,就几行代码。

子进程的进程号作为最后一个参数传给do_fork(),然后会被do_fork()直接作为返回值,这样父进程的t_fork()就会返回子进程的进程号。

即父进程返回子进程的进程号。

这时子进程还没有运行,它要等到调度器去调度它,即父进程执行t_schedule()函数时。

如下图,t_schedule()函数直接调用了汇编函数switch_to(),把当前的进程t_prev切换到下一个进程t_next。

切换的关键数据就是task_t的rsp、rip项,保存当前进程的这两项,加载下一个进程的这两项。

上图是fork用法的示例代码,else if分支是子进程1,最底部的while是父进程0。

上图是main()函数,初始化进程管理结构,相当于在内核态。最后它手工设置一个0号进程,把自己降低到用户态。

只有这一个进程是手工设置的,其他都是fork()的。

task_t必须在main函数的栈上,否则修改了栈指针之后操作系统不允许压栈,毕竟我们不是“真内核”,没那么大的权限。

那么,我们就在main()里开一个大的局部变量的数组,用来存放task_t结构,把它的高地址作为“进程栈”。

t0_run()的代码必须要少,以防调用层级过多,导致栈增长时覆盖了低地址的task_t关键数据。

在Linux内核里编程也有这个特点,在栈上不能申请大的局部变量空间,因为栈是有限的,早期是4k之后是8k,一旦覆盖了低地址的task_struct关键数据,就挂了。

下图的switch_to()函数是进程切换的重点,它在调度器的调度算法选择完要切换的进程之后,具体执行进程切换。

我们就模拟了两个,自然也没啥调度算法选择了,只能0切换1,1切换0。

1,保存rbp寄存器。

保存图中L1标号的地址,x64没法直接mov两个内存变量,必须拿一个寄存器当中介,这里是rbp。

保存当前的rsp寄存器。

这些操作都是在prev进程的栈上。

2,movq %rdx, %rsp,这行切换寄存器rsp为next进程的栈,它之前作为函数的实参保存在rdx里。

从这开始,操作切换到了next进程的栈上。

也没啥操作了,直接跳转next进程的rip指令位置执行就行,它也作为函数形参保存在rcx里。

如果next进程不是第一次执行,这个地址就是标号L1,因为我们上次把它切换出去时保存的就是这个地址。

如果next进程是第一次执行,这个地址就是do_fork_first()函数的地址,因为我们在t_fork()里设置的就是这个地址。

do_fork_first()就是把表示返回值的rax寄存器设置为0,然后返回。这样子进程“从fork()返回”时的返回值就是0了。

我们这个例子是用户态的进程,即协程。

实际内核里进程切换,要复杂一些,还有页表切换之类的。

举报/反馈

linux fork 函数,Linux的fork()系统调用相关推荐

  1. linux 的fork函数原型,浅析fork()和底层实现

    记得以前初次接触fork()函数的时候,一直被"printf"输出多少次的问题弄得比较晕乎.不过,"黄天不负留心人".哈~ 终于在学习进程和进程创建fork相关 ...

  2. linux中fork函数详解,fork() 函数详解

    fork() 函数是 linux/unix 下一种特别的创建子进程的函数,它不同与 Windows,这个函数在执行成功后会有两个返回值,一个返回值==0代表创建了子进程,一个返回值大于0代表还是当前程 ...

  3. linux popen 函数,Linux下使用popen()执行shell命令

    函数原型: #include "stdio.h" FILE popen( const char command, const char* mode ) 参数说明: command: ...

  4. linux timerfd_settime函数,Linux的timerfd分析

    timerfd是Linux为用户程序提供的一个定时器接口.这个接口基于文件描述符,所以能够被用于select/poll的应用场景. 1.使用方法 timerfd提供了如下接口供用户使用 timerfd ...

  5. linux brk函数,Linux sbrk/brk函数使用整理

    sbrk/brk: brk和sbrk主要的工作是实现虚拟内存到内存的映射.在GNUC中,内存分配是这样的: 每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只 ...

  6. linux receive函数,Linux网络 - 数据包的接收过程

    的方式写入到指定的内存地址,该地址由网卡驱动分配并初始化.注: 老的网卡可能不支持DMA,不过新的网卡一般都支持. 3: 网卡通过硬件中断(IRQ)通知CPU,告诉它有数据来了 4: CPU根据中断表 ...

  7. linux线程调度函数,Linux调度策略及线程优先级设置

    Linux内核的三种调度策略: 1,SCHED_OTHER 分时调度策略, 2,SCHED_FIFO实时调度策略,先到先服务.一旦占用cpu则一直运行.一直运行直到有更高优先级任务到达或自己放弃 3, ...

  8. linux sysconf函数,linux c sysconf函数 得到系统配置

    函数原型: NAME sysconf - Get configuration information at runtime SYNOPSIS #include long sysconf(int nam ...

  9. linux blind函数,Linux网络编程入门

    上一节创建socket时,仅指定了协议类型以及服务类型,但是没有指定具体的socket地址. bind函数含义如词义,给上述创建的套接字socket绑定一个socket地址. 比喻如下:我们创建了so ...

最新文章

  1. activity 生命周期_死磕Android_App 启动过程(含 Activity 启动过程)
  2. 社交牛逼症研发小哥的校招和入职初体验
  3. Java 设计模式之观察者模式
  4. C语言烧写C51单片机的线,51单片机烧写程序过程以及详细说明【图文】
  5. golang修改文件的最后访问时间,最后修改时间
  6. bash中将字符串split成数组的方法
  7. 宇轩网络面试题目PHP,二十道接地气的php面试题,让你直接通过面试!就此奉上~...
  8. 17 WM配置-策略-激活存储区搜索(Storage Section Search)
  9. 创业者没有周末,但有周期
  10. OpenWRT开源项目论坛遭未授权访问,可被用于供应链攻击
  11. 【剑指 offer】(48)—— 不能被继承的类
  12. Linux通过Shell进行数学运算
  13. 谷歌验证码无法显示问题
  14. (Josephus )约瑟夫环问题 C语言实现
  15. 大地测量学笔记 : 高斯克吕格投影
  16. jdk 6u45 下载地址
  17. 基于51单片机的模拟信号检测系统
  18. Vue工程测试Element-UI插件是否可用步骤
  19. redmine backlogs的tracker使用
  20. css 设置行内元素顶部对齐

热门文章

  1. VR 应用设计的 8 个建议
  2. XFS文件系统的优点及缺点
  3. DNS原理/解析过程
  4. Cocos2d-x 3D渲染技术 (二)
  5. 初识Volley(四)
  6. 视频号违规、流量、变现...13个常见问题合集
  7. 预约服务类APP都有哪些功能?
  8. h2数据库连接mysql_H2数据库简单使用操作
  9. WebStorm激活码存储
  10. Vue进阶(四十三):Vuex之Mutations详解