深圳市网站制作公司,制作wordpress主题,中国建设银行信用卡旅游卡服务网站,浦东教育网站官网linux源码阅读——进程管理#xff08;1#xff09; 1. 进程的基本介绍1.1 linux中进程和线程的区别1.2 task_struct中的基本内容1.3 命名空间ns(namespace)命名空间结构图Linux 中的命名空间类型 1.4 进程标识符 2. 创建一个进程的流程2.1 CLONE宏2.2 创建进程系统调用1. do… linux源码阅读——进程管理1 1. 进程的基本介绍1.1 linux中进程和线程的区别1.2 task_struct中的基本内容1.3 命名空间ns(namespace)命名空间结构图Linux 中的命名空间类型 1.4 进程标识符 2. 创建一个进程的流程2.1 CLONE宏2.2 创建进程系统调用1. do_fork流程——v6.9-kernel_clone2. do_fork中copy_process流程(1). 标志冲突判断(2). dup_task_struct——分配task空间(3). 检查用户的进程数量限制(4). copy_creds复制或共享证书(5). 检查线程数量限制(6). sched_fork——设置调度器相关参数(7). copy_xxx——根据CLONE_FLAG复制或共享资源 3. do_fork中的wake_up_new_task流程 3. 进程的退出3.1 退出概念简介3.2 exit_group线程组退出1. exit_group简介2. exit_group具体流程 3.3 kill 注 图片来自《Linux内核深度解析 基于ARM64架构的Linux 4.x内核》(余华兵)
1. 进程的基本介绍
1.1 linux中进程和线程的区别
在课本教学中我们习惯将进程和线程看作两种差别很大的东西但是在实际的linux系统中无论是进程还是线程都由所谓PCB(process control block) 也就是我们的 task_struct表示。 进程的虚拟地址空间分为用户虚拟地址空间和内核虚拟地址空间所有进程共享内核虚拟地址空间每个进程有独立的用户虚拟地址空间。 先说结论 进程——独立拥有用户虚拟地址空间用户线程——共享用户虚拟地址空间内核线程——没有用户虚拟地址空间
那么大家就要问了什么是用户虚拟地址空间(mm_struct)这个在讲调度的时候介绍
1.2 task_struct中的基本内容
成员说明volatile long state进程的状态使用volatile关键字确保读取都能得到最新的值void *stack指向内核栈 进程在内核态下的运行“装备”确保进程在进入内核模式时能够正确恢复上下文并安全执行内核代码pid_t pid全局的进程号,同一PID命名空间下pid唯一pid_t tgid全局的线程组标识符在一个多线程进程中多个线程会有不同的 pid但它们的 tgid 会相同struct pid_link pids[PIDTYPE_MAX];进程号进程组标识符和会话标识符struct task_struct __rcu *real_parent;real_parent指向真实的父进程, rcu(read-copy-update)并发编程机制struct task_struct __rcu *parent;parent指向父进程如果进程被另一个进程通常是调试器使用系统调用ptrace跟踪那么父进程是跟踪进程否则和real_parent相同struct task_struct *group_leader指向线程组的组长const struct cred __rcu *real_cred;real_cred指向主体和真实客体证书const struct cred __rcu *cred;cred指向有效客体证书。通常情况下cred和real_cred指向相同的证书但是cred可以被临时改变char comm[TASK_COMM_LEN]进程名称int prio, static_prio, normal_prio; unsigned int rt_priority; unsigned int policy;调度策略和优先级,dl,rt,cfscpumask_t cpus_allowed允许进程在哪些处理器上运行,处理器亲和性struct mm_struct *mm,*active_mm指向内存描述符进程mm和active_mm指向同一个内存描述符内核线程mm是空指针当内核线程运行时active_mm指向从进程借用的内存描述符struct fs_struct *fs文件系统信息主要是进程的根目录和当前工作目录struct files_struct *files打开文件表struct nsproxy *nsproxy命名空间代理struct signal_struct *signal; struct sighand_struct *sighand; sigset_t blocked, real_blocked; sigset_t saved_sigmask; struct sigpending pending;信号处理,signal_struct 用于存储和管理进程的信号状态。sighand_struct 管理进程的信号处理程序。blocked 和 real_blocked 控制进程哪些信号被阻塞避免信号干扰进程执行。saved_sigmask 保存和恢复进程的信号掩码状态。sigpending 存储待处理的信号。
1.3 命名空间ns(namespace) 和虚拟机相比容器是一种轻量级的虚拟化技术直接使用宿主机的内核使用命名空间隔离资源。 命名空间Namespace是 Linux 内核中的一种重要特性它用于隔离不同进程的资源使得多个进程能够在同一台机器上“仿佛”运行在独立的系统中。命名空间技术是实现容器如 Docker的核心技术之一。命名空间通过提供资源的隔离使得不同进程或容器能够有自己独立的资源视图如进程号PID、网络、挂载等。 命名空间结构图 Linux 中的命名空间类型
Linux 支持多种类型的命名空间每种命名空间用于隔离系统的某种资源。以下是 Linux 中常见的几种命名空间类型 进程号命名空间PID Namespace 进程号命名空间使得每个进程可以拥有自己的进程号PID。在不同的进程号命名空间中同一个 PID 号可以表示不同的进程。父进程的 PID 在子命名空间内不再是唯一的而是局限于该命名空间中。比如PID 1 代表的是该命名空间中的第一个进程而不是宿主机上的第一个进程。这种隔离有助于容器化应用的管理因为它们在容器内可以拥有从 1 开始的 PID而与宿主机的进程号不冲突。 挂载命名空间Mount Namespace 挂载命名空间允许不同的进程在不同的命名空间中看到不同的文件系统视图。它能够实现容器内进程看到的文件系统与宿主机或其他容器内进程看到的文件系统完全不同。例如在容器中你可以挂载不同的目录而这些挂载操作不会影响宿主机的文件系统。挂载命名空间是实现文件系统隔离的基础。 网络命名空间Network Namespace 网络命名空间提供了进程之间网络资源如 IP 地址、路由表、网络设备等的隔离。每个网络命名空间有自己的网络接口、路由表、防火墙等配置。这样容器或进程在不同的网络命名空间中彼此之间不能直接通信除非通过某种方式显式地配置网络连接。网络命名空间使得容器能够拥有自己的 IP 地址甚至在不同的容器之间实现隔离的虚拟网络。 IPC 命名空间IPC Namespace IPC 命名空间用于隔离进程间通信IPC机制如信号量、消息队列和共享内存。在不同的 IPC 命名空间中进程看到的共享内存段、消息队列等资源是隔离的。即使两个进程有相同的 PID它们也不能相互访问对方的 IPC 资源。 UTS 命名空间UTS Namespace UTS 命名空间用于隔离主机名和域名系统DNS信息。在不同的 UTS 命名空间中进程可以拥有独立的主机名和域名进而可以在容器中使用不同的主机名而不影响宿主机。 用户命名空间User Namespace 用户命名空间用于隔离进程的用户和组 IDUID/GID。在一个用户命名空间内进程可以有一个与宿主机完全不同的 UID 和 GID 映射。例如容器中的进程可以运行在 UID 0即 root 用户而在宿主机上可能对应的是一个普通用户。这使得容器中的进程能够具有 root 权限但在宿主机上却是以普通用户身份运行从而提高安全性。 时间命名空间Time NamespaceLinux 5.6 及以后版本 时间命名空间允许进程有自己独立的时间视图。每个时间命名空间可以有独立的系统时间即时间和时区设置。这使得容器能够有自己的时钟和时间管理系统而不依赖于宿主机的时间。 cgroup 命名空间Cgroup NamespaceLinux 4.5 及以后版本 cgroup 命名空间用于隔离进程的控制组cgroup信息。cgroup 是一种内核机制用于对进程进行资源限制、优先级调度等管理。cgroup 命名空间允许每个进程组如容器有自己独立的 cgroup 层次结构。 命名空间Namespace是 Linux 内核提供的一种资源隔离机制它通过为进程提供独立的资源视图确保不同进程或容器之间的隔离。命名空间的类型包括 PID、网络、挂载、IPC、UTS、用户等这些命名空间在容器化技术、进程管理、安全性等方面起着关键作用。通过使用命名空间Linux 能够在同一台机器上运行多个互相隔离的进程或容器从而实现更高效和安全的资源管理。 1.4 进程标识符
进程标识符 pid线程组标识符 tgid进程组标识符 pgid会话标识符 sid sid是多个兄弟在一起(shell)pgid是爸爸带一堆儿子(fork) 会话和进程组被设计用来支持 shell 作业控制shell 为执行单一命令或者管道的进程创建一个进程组 2. 创建一个进程的流程
2.1 CLONE宏
想要了解进程的创建流程首先需要了解clone都具备哪些标识
标志类别作用CSIGNAL信号相关标志子进程退出时发送给父进程的信号掩码。CLONE_VM资源共享标志父子进程共享虚拟内存空间通常用于线程。CLONE_FS资源共享标志父子进程共享文件系统信息如当前工作目录、根目录等。CLONE_FILES资源共享标志父子进程共享打开的文件描述符。CLONE_SIGHAND资源共享标志父子进程共享信号处理程序和阻塞信号。CLONE_PIDFD进程级别标志在父进程中为子进程创建一个 pidfdPID文件描述符。CLONE_PTRACE进程级别标志允许追踪继续父进程可以继续对其子进程进行追踪。CLONE_VFORK进程级别标志父进程希望子进程在释放其内存时唤醒父进程。CLONE_PARENT进程级别标志子进程和父进程保持相同的父进程。CLONE_THREAD线程/进程级别标志子进程和父进程属于同一个线程组通常用于线程。CLONE_NEWNS命名空间相关标志创建一个新的挂载命名空间。CLONE_SYSVSEM进程级别标志父子进程共享 System V 信号量SEM_UNDO语义。CLONE_SETTLS线程级别标志为子进程创建新的 TLS线程本地存储。CLONE_PARENT_SETTID线程级别标志将线程标识符 (TID) 设置到父进程的 parent_tid 指向的位置。CLONE_CHILD_CLEARTID线程级别标志线程退出时清除其 TID线程标识符。CLONE_DETACHED进程级别标志已废弃忽略。CLONE_UNTRACED进程级别标志如果设置了该标志父进程不能强制对子进程进行追踪。CLONE_CHILD_SETTID线程级别标志子进程首次调度时将 TID 设置到 child_tid 指向的位置。CLONE_NEWCGROUP命名空间相关标志创建一个新的 cgroup控制组命名空间。CLONE_NEWUTS命名空间相关标志创建一个新的 UTSUNIX时间共享命名空间通常用于主机名和域名的隔离。CLONE_NEWIPC命名空间相关标志创建一个新的 IPC进程间通信命名空间。CLONE_NEWUSER命名空间相关标志创建一个新的用户命名空间通常用于进程的用户和组 ID 隔离。CLONE_NEWPID命名空间相关标志创建一个新的 PID进程标识符命名空间。CLONE_NEWNET命名空间相关标志创建一个新的网络命名空间。CLONE_IO进程级别标志子进程与父进程共享 I/O 上下文I/O 调度等。
2.2 创建进程系统调用
1fork分叉子进程是父进程的一个副本采用了写时复制的技术。 2vfork用于创建子进程之后子进程立即调用 execve 以装载新程序的情况。为了避免复制物理页父进程会睡眠等待子进程装载新程序。现在 fork 采用了写时复制的技术vfork 失去了速度优势已经被废弃。 3clone克隆可以精确地控制子进程和父进程共享哪些资源。这个系统调用的 主要用处是可供 pthread 库用来创建线程。 clone 是功能最齐全的函数参数多使用复杂fork 是 clone 的简化函数。 那么在执行这三个系统调用实际执行的是do_fork函数注意在当前(2025/4)最新linux版本6.9中do_fork被更换为kernel_clone(), 具体执行流程和do_fork大体相同主要变更在kernel_clone 扩展了 do_fork 的功能并且增加了更多针对不同类型进程创建的支持特别是在 clone() 调用中引入了更多控制参数和特性。
1. do_fork流程——v6.9-kernel_clone
在v6.9版本的内核中do_fork被替换取而代之的是kernel_clone两者核心流程类似
调用函数 copy_process 以创建新进程关于第二个步骤中判断CLONE_PARENT_SETTID的操作——CLONE_PARENT_SETTID这是 clone() 调用中的一个标志表示 父进程想要在子进程创建时将子进程的 PID 设置到某个指定位置。这个标志位指示内核将子进程的 PID 写入父进程传入的 parent_tid 指针。 具体作用在一些特定的多线程应用或线程库中父进程或创建线程的控制者希望在用户空间中直接获得新创建线程或进程的 PID便于后续的管理比如设置线程的调度策略、处理进程间通信等。CLONE_PARENT_SETTID 可以帮助父进程或控制者直接获取子进程的 PID避免了额外的查询操作。 调用函数 wake_up_new_task 以唤醒新进程。如果是系统调用 vfork那么当前进程等待子进程装载程序。
2. do_fork中copy_process流程
创建fork新进程的主要工作由函数 copy_process 实现 官方注释——只执行复制但是不启动state设置为TASK_NEW This creates a new process as a copy of the old one, but does not actually start it yet. It copies the registers, and all the appropriate parts of the process environment (as per the clone flags). The actual kick-off is left to the caller. 接下来我们详细解读一下copy_process中每个流程
(1). 标志冲突判断 CLONE_NEWNS CLONE_FS: CLONE_NEWNS 会创建一个新的挂载命名空间意味着新的进程有一个独立的根目录根文件系统。而 CLONE_FS 是要求多个进程共享文件系统信息包括根目录。如果同时设置这两个标志会导致根目录和文件系统信息冲突因此是无效的。 CLONE_NEWUSER CLONE_FS: CLONE_NEWUSER 创建新的用户命名空间使得新的进程具有独立的用户身份。而 CLONE_FS 会共享文件系统信息这会导致新进程在文件系统方面不独立违反了用户命名空间的隔离原则因此这两个标志不能同时使用。 CLONE_THREAD !CLONE_SIGHAND: 线程组的进程必须共享信号处理程序。因此当设置 CLONE_THREAD 时必须设置 CLONE_SIGHAND 来共享信号处理程序。如果没有设置 CLONE_SIGHAND则无法确保线程组的一致性导致冲突。 CLONE_SIGHAND !CLONE_VM: 当进程共享信号处理程序CLONE_SIGHAND时必须共享虚拟内存空间CLONE_VM。如果不共享虚拟内存信号处理程序会因进程间内存空间不同而无法正常工作因此此组合会产生冲突。 CLONE_PARENT SIGNAL_UNKILLABLE: CLONE_PARENT 要求子进程的父进程为当前进程。全局 init 进程init是不可杀死的且没有父进程因此不允许全局 init 进程创建其他兄弟进程。否则会违反进程树结构产生冲突。 CLONE_THREAD (CLONE_NEWUSER | CLONE_NEWPID): 线程不允许跨越用户或 PID 命名空间。如果创建一个线程时设置了 CLONE_NEWUSER 或 CLONE_NEWPID则该线程将进入一个不同的命名空间这会破坏线程组的共享因此是无效的。 CLONE_PIDFD CLONE_DETACHED: 如果设置了 CLONE_PIDFD表示父进程会持有子进程的 PID 文件描述符允许父进程跟踪子进程。而 CLONE_DETACHED 表示子进程是分离的不需要父进程等待因此不能同时设置这两个标志。分离进程不应持有 PID 文件描述符。
(2). dup_task_struct——分配task空间 函数 dup_task_struct函数 dup_task_struct 为新进程的进程描述符分配内存把当前进程的进程描述符复制一份为新进程分配内核栈。 内核栈——task_struct中的stack指向内核栈 分配内核栈和 task_struct 所需空间 每个进程在内核中有一块私有栈内核栈和 task_struct 一起分配。 复制当前进程的 task_struct 数据到新结构体中 这是“浅拷贝”后续会由 copy_xxx() 函数根据CLONE_FLAG做深拷贝处理比如 copy_mm()、copy_files() 等。 初始化调试字段、引用计数等 比如清除 task_struct-stack_canary初始化调试状态。
(3). 检查用户的进程数量限制
对于普通用户如果创建的进程数量超限则失败 对于根用户因为根用户默认拥有忽略资源限制的权限CAP_SYS_RESOURCE和系统管理权限CAP_SYS_ADMIN可以创建
(4). copy_creds复制或共享证书
什么是 cred 证书? 回答 cred全称 struct cred是 Linux 内核中用于描述进程安全相关信息的一种核心数据结构。它就是**“进程的身份证 安全通行证”内核通过它判断当前进程能不能干某件事**。 cred 结构体包含什么
这个结构体定义在 include/linux/cred.h 中里面包含了以下这些字段简化版
字段含义uid, gid实际用户/组 IDeuid, egid有效用户/组 IDsuid, sgid保存的用户/组 ID用于切换身份fsuid, fsgid文件系统相关的用户/组 ID用于访问控制cap_inheritable可继承的能力capabilitiescap_permitted被允许的能力cap_effective当前生效的能力实际起作用的cap_bsetbounding set允许继承的最大能力集合user指向 struct user_struct跟踪用户的资源使用比如进程数security安全模块使用如 SELinux、AppArmor 作用是什么
内核中几乎所有需要安全判断的操作比如
访问文件打开 socket调用某些系统调用比如 mount、kill、ptrace进入 namespace修改资源限制获取 debug 权限如 /proc/*
都会通过 current-cred 来做决策。
比如
if (capable(CAP_SYS_ADMIN)) {// 允许系统管理操作
}这里的 capable() 内部其实就是读取当前线程的 cred-cap_effective看看有没有这个 capability。 谁用它
除了内核自身判断权限外像
ptrace()setuid(), setgid()capset()LSM如 SELinux容器安全模型
都会操作或依赖 cred。
一句话总结 cred是进程的安全身份信息它决定了一个进程能干什么、能访问什么、有没有权限。 对于kernel_clone(do_fork)copy_cred() 如果设置了标志 CLONE_THREAD即新进程和当前进程属于同一个线程组那么新进程和当前进程共享证书。 否则复制cred。 (5). 检查线程数量限制 全局变量nr_threads 存放当前的线程数量max_threads存放允许创建的线程最大数量默认值是 MAX_THREADS。 如果线程数量达到允许的线程最大数量那么不允许创建新进程。 (6). sched_fork——设置调度器相关参数
将task-state设为TASK_NEW确保新进程不会被运行也不会被信号唤醒不会被加入就绪队列rq(runqueue)把新进程的调度优先级设置为当前进程的正常优先级 因为当前进程可能因为占有实时互斥锁而被临时提升了优先级 unlikely检查是否设置了SCHED_RESET_ON_FORK 标志要求创建新进程时把新进程的调度策略和优先级设置为默认值拒绝dl(dealline)任务forkSMP 多核支持初始化
(7). copy_xxx——根据CLONE_FLAG复制或共享资源
Linux 内核在创建新进程时执行的一系列子系统状态复制流程它的作用是将父进程的资源、状态等复制或共享给子进程以确保新进程具有完整的运行环境。 ✅ 步骤及作用一览表 在5-10步骤中只有相同线程组的线程间才会进行共享人话:一个老爹生的
步骤函数调用作用失败时回滚标签1perf_event_init_task(p, clone_flags)初始化 perf 事件监控性能分析支持bad_fork_sched_cancel_fork2audit_alloc(p)初始化审计上下文用于安全审计audit 子系统bad_fork_cleanup_perf3shm_init_task(p)初始化与 System V 共享内存相关的结构无回滚无失败4security_task_alloc(p, clone_flags)安全模块如 SELinux相关初始化bad_fork_cleanup_audit5copy_semundo(clone_flags, p)复制 System V 信号量 undo 状态bad_fork_cleanup_security6copy_files(clone_flags, p, args-no_files)复制或共享文件描述符表如 open filesbad_fork_cleanup_semundo7copy_fs(clone_flags, p)复制或共享文件系统信息如 cwd、rootbad_fork_cleanup_files8copy_sighand(clone_flags, p)复制或共享信号处理器signal handlerbad_fork_cleanup_fs9copy_signal(clone_flags, p)复制或共享信号状态阻塞信号集等bad_fork_cleanup_sighand10copy_mm(clone_flags, p)复制或共享内存描述符内存空间bad_fork_cleanup_signal11copy_namespaces(clone_flags, p)创建或共享命名空间如 UTS、IPC、Mount 等bad_fork_cleanup_mm12copy_io(clone_flags, p)复制或共享 IO 上下文如 IO调度器相关bad_fork_cleanup_namespaces13copy_thread(p, args)初始化线程状态栈、寄存器、TLS 等bad_fork_cleanup_io
3. do_fork中的wake_up_new_task流程
那么在结束copy_process流程后一个新的进程或线程就创建成功了接下来就是要去运行它还记得在copy_process()/sched_fork()中将task-state置为TASK_NEW的作用吗那么在wake_up_new_task中把新进程的状态从 TASK_NEW 切换到 TASK_RUNNING。 在 SMP 系统上创建新进程是执行负载均衡的绝佳时机为新进程选择一个负载最轻的处理器。 这时使用__set_task_cpu()将任务放到负载最轻的处理器上实现负载均衡 流程
调整state为running__set_task_cpu()负载均衡锁rq更新rq释放锁
至此一个新进程/线程的创建就正式结束了。
3. 进程的退出
3.1 退出概念简介
退出又分为
主动退出 exit,exit_group 被动退出 kill,tgkill 被信号通知退出 当进程退出的时候根据父进程是否关注子进程退出事件处理存在如下差异。 1如果父进程关注子进程退出事件那么进程退出时释放各种资源只留下一个空的进程描述符变成僵尸进程(即task_struct没有被完全释放)[因为系统需要保留一些关于子进程的信息供父进程查询例如子进程的退出状态]发送信号 SIGCHLDCHLD 是 child 的缩写通知父进程父进程在查询进程终止的原因以后回收子进程的进程描述符。 2如果父进程不关注子进程退出事件那么进程退出时释放各种资源释放进程描述符自动消失。进程默认关注子进程退出事件如果不想关注可以使用系统调用 sigaction 针对信号SIGCHLD 设置标志 SA_NOCLDWAITCLD 是 child 的缩写以指示子进程退出时不要变成僵尸进程或者设置忽略信号 SIGCHLD。 3.2 exit_group线程组退出
1. exit_group简介
exit_group() 可以被看作是 信号传递式退出函数 的一种典型代表它的语义是终止整个线程组即进程的所有线程。 和普通 exit() 的区别
函数影响范围特点exit()仅当前线程不会终止进程中其他线程exit_group()当前线程 同一进程中的所有线程会向整个线程组中的所有线程发出终止信号 内核行为概览
exit_group() 最终调用的是 do_group_exit()它会设置 signal-group_exit_code标记线程组整体退出然后会逐个 wake up 其他线程让它们也进入退出流程所有线程会逐个进入 do_exit()释放资源、触发钩子等。 为什么说它是“信号式”的
尽管 exit_group() 本质是一个系统调用但其实现通过内部机制模拟了类似“信号传播”的退出方式 通过共享的 struct signal_struct 对线程组成员**“广播”退出状态**这就像是“向整个线程组传递了一个退出意图”。
2. exit_group具体流程 假设一个线程组有两个线程称为线程 1 和线程 2线程 1 调用 exit_group 使线程组退 出线程 1 的执行过程如下。 1把退出码保存在信号结构体的成员 group_exit_code 中传递给线程 2。 2给线程组设置正在退出的标志。 3向线程 2 发送杀死信号然后唤醒线程 2让线程 2 处理杀死信号。 4线程 1 调用函数 do_exit 以退出。 线程 2 退出的执行流程如下图所示线程 2 准备返回用户模式的时候发现收到 了杀死信号于是处理杀死信号调用函数 do_group_exit函数 do_group_exit 的执行过 程如下。
3.3 kill
系统调用 kill源文件“kernel/signal.c”负责向线程组或者进程组发送信号。 1如果参数 pid 大于 0那么调用函数 kill_pid_info 来向线程 pid 所属的线程组发送信号。 2如果参数 pid 等于 0那么向当前进程组发送信号。 3如果参数 pid 小于−1那么向组长标识符为-pid 的进程组发送信号。 4如果参数 pid 等于−1那么向除了 1 号进程和当前线程组以外的所有线程组发送信号。