当前位置: 首页 > news >正文

Linux内核进程创建流程

本文代码基于Linux5.10
内容主要参考《Linux内核深度解析》余华兵

当Linux内核要创建一个新进程时, 流程大致如下

ret = fork();
if (ret == 0) {/* 子进程装载程序 */ret = execve(filename, argv, envp);
} else if (ret > 0) {/* 父进程 */
}

大致可以分为创建新进程和装载程序这两个过程。

创建新进程

Linux中创建新进程有两个系统调用, 分别是clone和fork, 其定义如下:

kernel/fork.c
SYSCALL_DEFINE0(fork) {#ifdef CONFIG_MMUstruct kernel_clone_args args = {.exit_signal = SIGCHLD,};return kernel_clone(&args);
#else/* can not support in nommu mode */return -EINVAL;
#endif
}
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,int, stack_size,int __user *, parent_tidptr,int __user *, child_tidptr,unsigned long, tls) {struct kernel_clone_args args = {.flags		= (lower_32_bits(clone_flags) & ~CSIGNAL),.pidfd		= parent_tidptr,.child_tid	= child_tidptr,.parent_tid	= parent_tidptr,.exit_signal	= (lower_32_bits(clone_flags) & CSIGNAL),.stack		= newsp,.tls		= tls,};return kernel_clone(&args);
}

可以理解为fork是clone的简化版本, clone可以更精确的控制创建进程的行为,我们在创建线程时,就是使用的clone(没错, 在Linux里面, 线程实际上也是进程)。

clone 和 fork 都会调用kernel_clone 这个函数去创建进程,只不过两者传递的参数不同。

Linux 目前通过kernel_clone_args 这个数据结构来传递参数。

include/linux/sched/task.h
struct kernel_clone_args {u64 flags;int __user *pidfd;int __user *child_tid;int __user *parent_tid;int exit_signal;unsigned long stack;unsigned long stack_size;unsigned long tls;pid_t *set_tid;/* Number of elements in *set_tid */size_t set_tid_size;int cgroup;struct cgroup *cgrp;struct css_set *cset;
};

flags : clone 标志。

stack : 只在创建线程时有意义, 用来指定线程的用户栈的地址

stack_size:只在创建线程时有意义, 用来指定线程的用户栈的大小

创建新进程的流程大致如下:

  1. 调用函数copy_process 创建新进程
  2. 调用函数wake_up_new_task 唤醒新进程。

copy process

copy process的流程如下:

1.检查标志是否合法。

kernel/fork.c/** Don't allow sharing the root directory with processes in a different* namespace*/if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))return ERR_PTR(-EINVAL);if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))return ERR_PTR(-EINVAL);.....

2.dup_task_struct。 已当前进程为模板, 创建task_struct数据结构

这里面会分配task_struct 的数据结构, 并分配内核栈。

内核栈也是一个slab。

kernel/fork.c
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk,int node)
{unsigned long *stack;stack = kmem_cache_alloc_node(thread_stack_cache, THREADINFO_GFP, node);stack = kasan_reset_tag(stack);tsk->stack = stack;return stack;
}
void thread_stack_cache_init(void)
{thread_stack_cache = kmem_cache_create_usercopy("thread_stack",THREAD_SIZE, THREAD_SIZE, 0, 0,THREAD_SIZE, NULL);BUG_ON(thread_stack_cache == NULL);
}

3.检查用户的进程数量限制

kernel/fork.cif (atomic_read(&p->real_cred->user->processes) >=task_rlimit(p, RLIMIT_NPROC)) {if (p->real_cred->user != INIT_USER &&!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))goto bad_fork_free;}

在用户空间, 可以通过ulimit -u 来设置用户最大可以创建的进程数量。

4.copy_creds

调用copy_cread 复制或者共享证书, 如果新进程和当前进程属于同一个线程组, 那么他们共享证书。

5. 检查线程数量限制

kernel/fork.cif (data_race(nr_threads >= max_threads))goto bad_fork_cleanup_count;nr_threads 会在每次创建进程/线程后+1

6.sched_fork

设置调度器相关的参数

7.复制或者共享资源

这里会复制虚拟内存,文件, 文件系统数据, 信号处理数据等各种资源。 这里重点介绍一下copy_thread 这个流程, 这里会复制进程的各种寄存器。

arch/arm64/kernel/process.c
int copy_thread(unsigned long clone_flags, unsigned long stack_start,unsigned long stk_sz, struct task_struct *p, unsigned long tls)
{struct pt_regs *childregs = task_pt_regs(p);memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));   /*        1         *//** In case p was allocated the same task_struct pointer as some* other recently-exited task, make sure p is disassociated from* any cpu that may have run that now-exited task recently.* Otherwise we could erroneously skip reloading the FPSIMD* registers for p.*/fpsimd_flush_task_state(p);ptrauth_thread_init_kernel(p);if (likely(!(p->flags & PF_KTHREAD))) {            /*        2         */         *childregs = *current_pt_regs();childregs->regs[0] = 0;													/*        3         *//** Read the current TLS pointer from tpidr_el0 as it may be* out-of-sync with the saved value.*/*task_user_tls(p) = read_sysreg(tpidr_el0);if (stack_start) {                         /*        4         */if (is_compat_thread(task_thread_info(p)))childregs->compat_sp = stack_start;elsechildregs->sp = stack_start;}/** If a TLS pointer was passed to clone, use it for the new* thread.*/if (clone_flags & CLONE_SETTLS)p->thread.uw.tp_value = tls;} else {                /*        5         *//*  * A kthread has no context to ERET to, so ensure any buggy* ERET is treated as an illegal exception return.** When a user task is created from a kthread, childregs will* be initialized by start_thread() or start_compat_thread().*/memset(childregs, 0, sizeof(struct pt_regs));childregs->pstate = PSR_MODE_EL1h | PSR_IL_BIT;p->thread.cpu_context.x19 = stack_start;p->thread.cpu_context.x20 = stk_sz;}p->thread.cpu_context.pc = (unsigned long)ret_from_fork; /*        6         */p->thread.cpu_context.sp = (unsigned long)childregs;ptrace_hw_copy_thread(p);return 0;
}

用户态相关的运行环境缓存在pt_regs 中, 内核态保存在thread结构体中。

(1) 获取pt_regs, 并初始化thread 结构体

(2) 对于用户进程的处理

(3) 设置返回值为0。(子进程fork返回0就是在这里设置)

(4) 设置线程的用户栈

(5) 对于内核进程的处理, 这里X19存储线程函数的地址,X20存放线程函数的参数

(6) 设置内核态的PC和SP值, 在发生进程切换时, 会切到原因的地方去

wake up new task

在新进程创建之后,会尝试去唤醒它,让它尽快得到执行, 其流程大致如下:

新进程第一次运行

前文说到,copy_thread是会把新进程的PC设置为ret_from_fork。

arch/arm64/kernel/entry.S
/** This is how we return from a fork.*/
SYM_CODE_START(ret_from_fork)bl	schedule_tailcbz	x19, 1f				// not a kernel threadmov	x0, x20blr	x19
1:	get_current_task tskb	ret_to_user
SYM_CODE_END(ret_from_fork)

在ret_from_fork中, 首先进行调度切换的清理工作(schedule_tail)。 如果是用户进程,调用ret_to_user返回用户空间, 如果是内核进程,X19存储线程函数的地址,X20存放线程函数的参数, 这里会跳转到x19所存储的函数地址执行。

装载程序

一般来说, 用户层会调用execve或者execveat 执行某个具体的程序。

int execve(const char *filename, char *const argv[ ], char *const envp[ ]);

用户程序一般是一个elf文件, 内核会按照elf文件的格式去解析它, 并设置PC到对应的entry。这部分内容不在此详细说明。

实例: init 进程的创建和运行

init 是kernel运行的第一个进程, 我们来看看它是怎么创建和运行起来。

rest_init中,会调用kernel_thread 创建init进程

init/main.c
noinline void __ref rest_init(void)
{.....pid = kernel_thread(kernel_init, NULL, CLONE_FS);.....
}pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{struct kernel_clone_args args = {.flags		= ((lower_32_bits(flags) | CLONE_VM |CLONE_UNTRACED) & ~CSIGNAL),.exit_signal	= (lower_32_bits(flags) & CSIGNAL),.stack		= (unsigned long)fn,.stack_size	= (unsigned long)arg,};return kernel_clone(&args);
}

可以看到kernel_thread其实也是调用kernel_clone创建线程,其中stack被设置成了入口函数,stack_size被设置成了参数。

在kernel_init中, 会尝试装载init进程。

init/main.c
static int __ref kernel_init(void *unused)
{
.....
if (ramdisk_execute_command) {ret = run_init_process(ramdisk_execute_command);if (!ret)return 0;pr_err("Failed to execute %s (error %d)\n",ramdisk_execute_command, ret);}
....
}

装载完成之后, 就会调转到用户态的init进程执行了。

相关文章:

Linux内核进程创建流程

本文代码基于Linux5.10 内容主要参考《Linux内核深度解析》余华兵 当Linux内核要创建一个新进程时, 流程大致如下 ret fork(); if (ret 0) {/* 子进程装载程序 */ret execve(filename, argv, envp); } else if (ret > 0) {/* 父进程 */ } 大致可以分为创建新…...

【03.04】大数据教程--HTTP协议和静态Web服务器

HTTP协议和静态Web服务器 HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的协议,它是Web上的基础通信协议。静态Web服务器是指能够提供静态内容(如HTML、CSS、JavaScript和图像文件)的服务器。 在本教程中&am…...

数据共享传输:台式机和笔记本同步文件!

为什么要在台式机和笔记本同步文件? “我想在台式机和笔记本同步文件。因为我工作时使用笔记本,在家里使用安装了Windows 10系统的台式机,我想要在笔记本和台式机之间同步应用程序、游戏、文档等。有没有一种可以在台式机和笔记本同步文件的…...

java设计模式(十二)代理模式

目录 定义模式结构角色职责代码实现静态代理动态代理jdk动态代理cglib代理 适用场景优缺点 定义 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。说简单点,代理模式就是设置一个中间代理来控制访问原目标对象,以达到…...

Umi微前端水印踩坑以及解决方案

最近公司需要在管理后台加一个水印方案~ 项目用的umi方案,以为就是改一个配置的问题,后来发现坑点还蛮多~ 希望此稳定能帮助到用umi 的你们. 一. 先来说说心路历程 坑点1 umi的水印适配只能在layout中进行配置,也就是路由配置中layout为false的页面无法配置水印,比如说登录页…...

Android RK3588-12 hdmi-in Camera方式支持NV24格式

hdmi-in Camera方式支持NV24格式 modified: hardware/interfaces/camera/device/3.4/default/ExternalCameraDevice.cpp modified: hardware/interfaces/camera/device/3.4/default/ExternalCameraDeviceSession.cpp diff --git a/hardware/interfaces/camera/device/3.4…...

Hive窗口函数详细介绍

文章目录 Hive窗口函数概述样本数据表结构表数据 窗口函数窗口聚合函数count()SQL演示 sum()SQL演示 avg()SQL演示 min()SQL演示 max()SQL演示 窗口分析函数first_value() 取开窗第一个值应用场景SQL演示 last_value()取开窗最后一个值应用场景SQL演示 lag(col, n, default_val…...

牛客网【c语言练习】

单选题 下面代码段的输出是(-12 ) int main() {int a3; printf("%d\n",(aa-a*a)); } aa-9,此时还是等于3,因为a*a只是运算,并没有赋值;之后再算a-9,运算之前a等于3,运算…...

C++类和对象(上)

文章目录 🦍1. 面向过程和面向对象🦧2. 类的引入🐶3. 类的定义🦮4. 类的访问控制和封装🍖4.1 访问限定符🍖4.2 封装 🐩5. 类的作用域🐅6. 类的实例化🐄7. 类的大小计算&a…...

JavaScript 数据透视表 DHTMLX Pivot Crack

DHTMLX Pivot JavaScript 数据透视表 - 强大的数据汇总和报告 使用我们的高速 JavaScript/HTML5 Pivot 组件可视化您的复杂数据,从而提高您的商业智能。 它可以帮助您以方便的方式汇总大型数据集。 主要特征 纯 JavaScript 库,可轻松与任何服务器端集成…...

QT链接库设置

以windows 平台为例&#xff0c;在.pro 文件中&#xff1a; 1 增加 INCLUDEPATH <头文件路径> DEPENDPATH <头文件路径> 2 LIBS -L<库目录路径> -l<库得名字> 3 设置MT、MTD、MD、MDD运行时库 win32:CONFIG(debug, debug|release): { QMAKE_CFLAGS_…...

零点起飞学Android——期末考试课本复习重点

目录 第一章 认识Android第二章 Android常见界面布局第三章 Android常用基本控件第四章 Android 高级控件第五章 Android菜单和对话框 第一章 认识Android 1. Android 界面设计被称为______。 答案&#xff1a;布局 2. Android中常见的布局包括______、______ 、______ 、____…...

Redis为什么快?

目录 Redis为什么快&#xff1f;渐进式ReHash全局哈希表渐进式ReHash 缓存时间戳 Redis为什么快&#xff1f; 纯内存访问&#xff1b; 单线程避免上下文切换&#xff1b; 渐进式ReHash、缓存时间戳&#xff1b; 前面两个都比较好理解&#xff0c;下面我们主要来说下 渐进式…...

Zabbix从入门到精通以及案例实操系列

1、Zabbix入门 1.1、Zabbix概述 Zabbix是一款能够监控各种网络参数以及服务器健康性和完整性的软件。Zabbix使用灵活的通知机制&#xff0c;允许用户为几乎任何事件配置基于邮件的告警。这样可以快速反馈服务器的问题。基于已存储的数据&#xff0c;Zabbix提供了出色的报告和…...

水声声波频率如何划分?水声功率放大器可将频率放大到20MHz吗?

水声声波频率如何划分&#xff1f;水声功率放大器可将频率放大到20MHz吗&#xff1f; 现如今我们可以在地球任意地区实现通信&#xff0c;是因为电磁波的作用。但是我们都知道海洋占了全球十分之七面积&#xff0c;电磁波在水下衰减速度太快&#xff0c;无法做到远距离传输&am…...

网络攻防技术--论文阅读--《基于自动数据分割和注意力LSTM-CNN的准周期时间序列异常检测》

英文题目&#xff1a;Anomaly Detection in Quasi-Periodic Time Series based on Automatic Data Segmentation and Attentional LSTM-CNN 论文地址&#xff1a;Anomaly Detection in Quasi-Periodic Time Series Based on Automatic Data Segmentation and Attentional LST…...

C++ 学习 ::【基础篇:08】:C++ 中 struct 结构体的认识【面试考点:C 与 C++ 中结构体的区别】

本系列 C 相关文章 仅为笔者学习笔记记录&#xff0c;用自己的理解记录学习&#xff01;C 学习系列将分为三个阶段&#xff1a;基础篇、STL 篇、高阶数据结构与算法篇&#xff0c;相关重点内容如下&#xff1a; 基础篇&#xff1a;类与对象&#xff08;涉及C的三大特性等&#…...

Electron开发:打包和发布 Electron 应用

https://start.spring.io/ 在线数据分析网站 https://tj.aldwx.com/ https://www.spsspro.com/ win10如何分屏 拖到边缘 Electron 环境搭建 https://www.electronjs.org/zh/docs/latest/tutorial/%E6%89%93%E5%8C%85%E6%95%99%E7%A8%8B electron 隐藏菜单 electron 标题栏 设…...

【每日一题Day222】LC1110删点成林 | dfs后序

删点成林【LC1110】 给出二叉树的根节点 root&#xff0c;树上每个节点都有一个不同的值。 如果节点值在 to_delete 中出现&#xff0c;我们就把该节点从树上删去&#xff0c;最后得到一个森林&#xff08;一些不相交的树构成的集合&#xff09;。 返回森林中的每棵树。你可以按…...

[ChatGPT] 从 GPT-3.5 到 GPT-5 的进化之路 | ChatGPT和程序员 : 协作 or 取代

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐如果觉得文章写的不错&#xff0c;欢迎点个关注一键三连&#x1f609;有写的不好的地方也欢迎指正&#xff0c;一同进步&#x1f601;…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...