《Linux内核设计与实现》读书笔记——进程管理

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

进程管理


进程概念

进程:进程是处于执行期的程序以及相关资源的总称
线程:线程是进程中活动的对象。每个线程都有独立的程序计数器、进程栈和寄存器。内核调度的是线程而不是进程

内核并不区分进程和线程,只是创建时传递给创建函数的参数不一样

进程结构

进程队列组成双向循环链表,类型为task_struct,此结构体最为重要,保存了进程的所有信息,进程栈中有thread_info结构,里面有指针指向task_struct结构体
thread_info结构体如下:

// 取自2.6.24内核版本 /include/asm-x86/thread_info_64.h
struct thread_info {
    struct task_struct  *task;      /* main task structure */
    struct exec_domain  *exec_domain;   /* execution domain */
    __u32           flags;      /* low level flags */
    __u32           status;     /* thread synchronous flags */
    __u32           cpu;        /* current CPU */
    int             preempt_count;  /* 0 => preemptable, <0 => BUG */

    mm_segment_t        addr_limit; 
    struct restart_block    restart_block;
};

thread_info结构放在进程内核栈的尾端,可以根据esp寄存器和栈大小计算出thread_info位置

task_struct结构体如下

// 取自2.6.24内核版本
struct task_struct {
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    void *stack;
    atomic_t usage;
    unsigned int flags; /* per process flags, defined below */
    unsigned int ptrace;

    int lock_depth;     /* BKL lock depth */

#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
    int oncpu;
#endif
#endif

    int prio, static_prio, normal_prio;
    struct list_head run_list;
    const struct sched_class *sched_class;
    struct sched_entity se;

#ifdef CONFIG_PREEMPT_NOTIFIERS
    /* list of struct preempt_notifier: */
    struct hlist_head preempt_notifiers;
#endif

    unsigned short ioprio;
    /*
     * fpu_counter contains the number of consecutive context switches
     * that the FPU is used. If this is over a threshold, the lazy fpu
     * saving becomes unlazy to save the trap. This is an unsigned char
     * so that after 256 times the counter wraps and the behavior turns
     * lazy again; this to deal with bursty apps that only use FPU for
     * a short time
     */
    unsigned char fpu_counter;
    s8 oomkilladj; /* OOM kill score adjustment (bit shift). */
#ifdef CONFIG_BLK_DEV_IO_TRACE
    unsigned int btrace_seq;
#endif

    unsigned int policy;
    cpumask_t cpus_allowed;
    unsigned int time_slice;

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
    struct sched_info sched_info;
#endif

    struct list_head tasks;
    /*
     * ptrace_list/ptrace_children forms the list of my children
     * that were stolen by a ptracer.
     */
    struct list_head ptrace_children;
    struct list_head ptrace_list;

    struct mm_struct *mm, *active_mm;

/* task state */
    struct linux_binfmt *binfmt;
    int exit_state;
    int exit_code, exit_signal;
    int pdeath_signal;  /*  The signal sent when the parent dies  */
    ......
}

进程状态

进程有五种状态:
1. TASK_RUNNING:运行态
2. TASK_INTERRUPTIBLE:进程阻塞,等待条件达成则设为运行态。也可被信号唤醒而投入运行
3. TASK_UNINTERRUPTIBLE:进程阻塞,等待条件达成则设为运行态。对信号不做响应
4. _TASK_TRACED:被其他进程跟踪
5. _TACK_STOPPED:进程停止
这里写图片描述

进程、线程创建

clone()->do_fock()系统调用

这里写图片描述
进程与线程的创建都通过clone()系统调用实现,它们传递给clone()不同的标志,然后clone()调用do_fork()。
do_fork完成了创建的大部分工作,do_fork中的主要函数为copy_process()
copy_process的主要工作如下:
1. 调用dup_task_struct()为新进程创建内核栈、thread_info结构和task_struct
2. 确保创建子进程后进程数目没有超过限制
3. 进程描述符内的许多成员都被清零或设置为初值,为了和父进程区分开
4. 子进程状态设置为TASK_UNINTERRUPTIBLE,保证它目前不会运行
5. 调用copy_flags更新task_struct的flags成员
6. 调用alloc_pid()为新进程分配PID
7. 根据clone()传入的参数,拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间等

vfork()

vfork不拷贝父进程的页表项
vfork执行时父进程被阻塞,直到子进程退出或执行exec(),子进程不能向地址空间写入

因为vfork不拷贝页表,不能通过子进程引用或修改父进程的变量,vfork失败后立即调用_exit(),不可调用exit(),因为exit()同时关闭了子进程和父进程的I/O流缓冲
因为写实拷贝的作用,vfork()相比fork()如今如今并没了多大优势,并且很多安全问题,最好使用fork()

线程创建

与创建进程相似,调用clone()时传递参数指明共享的资源
创建线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,0)
fork():clone(SIGCHLD,0) ???存疑,日后看源码查证
vfork():clone(CLONE_VFORK | CLONE_VM | SIGCHLD,0)

clone参数标志如下

参数含义
CLONE_FILES父子进程共享打开的文件
CLONE_FS父子进程共享文件系统信息
CLONE_SIGHAND父子进程共享信号处理程序及被阻断的信号
CLONE_VFORK调用vfork()父进程睡眠等待子进程唤醒
CLONE_VM父子进程共享地址空间
…………

内核线程

内核线程没有独立地址空间,只在内核空间运行,只能由内核线程创建,可被抢占和调度

struct task_struct *kthread_create(int (*threadfn)(void *data),
                   void *data,
                   const char namefmt[], ...);

/**
 * kthread_run - create and wake a thread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#define kthread_run(threadfn, data, namefmt, ...)              \
({                                     \
    struct task_struct *__k                        \
        = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
    if (!IS_ERR(__k))                          \
        wake_up_process(__k);                      \
    __k;                                   \
})

进程终结


这里写图片描述

清理工作

进程终结大部分工作靠do_exit()函数完成
1. 将task_struct的标志设为PF_EXITING
2. 调用del_timer_sync()删除任一内核定时器
3. 调用exit_mm()函数释放进程占用的mm_struct,如果没有别的进程使用,则释放
4. 调用sem_exit(),如果进程排队等待IPC信号,它则离开队列
5. 调用exit_files()和exit_fs()递减文件描述符,文件系统数据的引用计数。减为0则释放
6. 把task_struct中的exit_code设置为exit()提供的退出码
7. 调用exit_notify()向父进程发送信号,给子进程重新寻找养父,养父为线程组中的其他线程或init进程,把进程状态设为EXIT_ZOMBIE
8. 调用schedule()切换到新的进程,这是进程执行的最后一段代码,do_exit()永不返回

到此与进程相关的资源释放完毕,进程处于EXIT_ZOMBIE退出状态。它占用的内存只有内核栈,thread_info结构和task_struct结构,进程存在的唯一目的是向父进程提供信息

删除进程描述符

在父进程获得已终结子进程的信息后,或者通知内核它并不关注那些信息后,子进程的task_struct结构才被释放。
release_task()释放进程描述符:
1. 调用_exit_signal(),该函数调用_unhash_process(),后者调用detach_pid()从pidhash上删除该进程,同时从任务队列中删除
2. _exit_signal()释放目前僵尸进程占用的所有剩余资源
3. 如果这个进程是线程组最后一个进程,并且领头进程死掉,那么就是通知僵死的领头进程的父进程
4. release_task()调用put_task_struct()释放进程栈和thread_info结构所占的页,释放task_struct所占的slab高速缓存

孤儿进程和僵死进程

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程

由上述进程终结工作我们很容易知道孤儿进程不会造成危害,因为init()进程会接管它,完成清理工作。僵尸进程父进程没有调用wait()族函数来收集信息,无法完成它的清理工作,占用了进程描述符,系统内核栈,资源得不到释放,大量僵尸进程则会占用系统内存和进程描述符,使其他进程不能创建

小结

了解了进程管理的实现,因为内核源码太过与庞大,细枝末节牵扯的太多,没有过多的去读源码,从宏观架构上了解进程/线程是实现

参考如下:
《Linux内核设计与实现》
Linux 进程管理剖析
https://blog.csdn.net/vertor11/article/details/79252760

linux中进程和线程的区别(fork和pthread_create的区别) linux中进程和线程的区别 在linux中两者区别不大,底层都是通过clone函数来创建,只是参数不同,先上一个clone()参数标志: pthread_create使用的clone是:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,0) fork使用的clone是:clone(SIGCHLD,0) vfork使用的clone是:clone(CLONE_VFORK | CLONE_VM | SIGCHLD,0) 区别只是在于clone参数 阅读详情

相关推荐

linuxClone()函数详解

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); LinuxClone()函数详解 这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在Linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进

Jack小站 2万+

Linux内核设计实现读书笔记

Linux内核设计实现读书笔记,我的评价是,不行

weixin_42065178的博客 1098

Linux内核设计实现读书笔记 - 目录 (完结)

读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个《Linux内核设计实现读书笔记 的目录:   《Linux内核设计实现读书笔记(一)-内核简介 《Linux内核设计实现读书笔记(二)- 内核开发的准备 《Linux内核设计实现读书笔记(三)- Linux的进程 《Linux内核设...

weixin_33682790的博客 608

Linux内核设计实现读书笔记

linux内核简介 Linux系统的基础是内核,C库(库函数里会有些系统调用),工具集和系统的基本工具。 通常一个内核由负责响应中断的中断服务程序,负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间的内存管理程序,和网络、进程间通信等系统服务程序共同组成。 当硬件设备想要跟系统通信的时候,首先要发出一个异步的中断信号去打断处理器的执行,继而打断内核的执行。 为了保证中断服务程序能够...

zhougb3的博客 4874

Linux内核设计实现读书笔记进程管理

进程 进程并不仅仅局限于一段可执行程序代码。通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间,一个或多个执行线程,存放全局变量的数据段。 每个线程都拥有一个独立的线程栈,线程寄存器和程序计数器。 一般会在栈顶创建一个结构体struct Thread_Info,该数据结构中保存指向进程描述符task_struct的指针。 内核通过一个唯一的进程标识符PID来 标识每个进程 一个进程必然处于五种进程状态中的一种:TASK_RUNN

breakfei11的博客 499

Linux内核设计实现读书笔记三——进程管理(内附思维导图)

yangcs2009的专栏 1188

读书笔记-《Linux内核设计实现》-1 进程管理

本篇主要总结Linux操作系统中进程相关的基本内容。

Ocean1994的博客 813

Linux 内核设计实现》之《进程管理读书笔记

本文为《Linux 内核设计实现》第二版第三章《进程管理》的读书笔记

项脊轩 646

Linux内核设计实现读书笔记】(三)进程管理

进程管理

weixin_51681590的博客 572

读书笔记Linux内核设计实现-进程管理

文章目录1.进程2.进程描述符以及任务结构2.1 分配进程描述符2.2 进程描述符的存放--PID2.3 进程状态2.4 设置当前进程状态--set_task_state2.5 进程上下文2.6 进程家族树--所有的进程都是PID为1的init进程的后代3.进程创建--fork和exec族函数3.1 写时拷贝--Linux拥有进程快速执行能力原因之一3.2 fork()3.3 vfork()4.线...

wang 恒 的博客 736

linux内核设计实现读书笔记(三)linux进程管理

进程线程 ①进程就是处于执行期的程序,通常进程还包含挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程,还包含存放全局变量的数据段等。 ②线程是进程中活动的对象,每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。进程和线程是程序运行时状态,是动态变化的,进程和线程的管理操作(比如,创建,销毁等)都是有内核实现的。 ③我们使用操作系统就是为了运行用户程序,而内核调度的对象是线程,对linux而言,不严格区分进程和线程,线程就是一种特殊的进程,或

上官绝痕的专栏 1262

linux内核设计实现进程管理、进程调度读书笔记

近期比较清闲,决定要系统的一点一滴地好好学习,首先就从这本书开始,坚持每天都能更新!^_^内核源码树根目录描述:Directory Descriptionarch Architecture-specific sourcecrypto Crypto APIDocumentation...

weixin_34390996的博客 200

Linux内核设计实现读书笔记进程管理

1.进程描述符及任务结构      Linux内核把进程存放在叫做任务队列的双向循环链表中。链表中的每一项都是类型为task_struct,称为进程描述符的结构。进程描述符包含一个具体进程的所有信息。      task_struct相对较大,在32位的机器长,它大约有1.7K字节。进程描述符中的数据能完整的描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态,还有其他

yao_hust_iAC的专栏 1063

Linux内核设计实现——读书笔记2:进程管理

1.进程: (1)处于执行期的程序,但不止是代码,还包括各种程序运行时所需的资源,实际上进程是正在执行的 程序的实时结果。 (2)程序的本身并不是进程,进程是处于执行期的程序及其相关资源的总称。 (3)两个或两个以上并存的进程可以共享诸如打开的文件,地址空间等共享资源。 (4)在Linux中通常是调用fork()系统函...

anjl1296的博客 85

进程管理——linux内核设计实现读书笔记

进程

cleo321的专栏 479

Linux内核设计实现读书笔记目录

Linux内核设计实现读书笔记(一)—Linux内核简介 《Linux内核设计实现读书笔记(二)—从内核出发 《Linux内核设计实现读书笔记(三)—进程管理

chenlb200的博客 500

Linux内核设计实现读书笔记(一)--进程管理

讨论Linux操作系统抽象概念中最基本的一种:进程。

chengjaven的博客 531

Linux内核设计实现读书笔记(2)-进程管理

<br />Linux内核设计实现读书笔记(2)-进程管理<br />    今天读了第二章,大家分享一下我的心得。 <br />    1、linux系统中允许同时存在的进程的最大数目为32767,这是因为PID的最大值默认设置为32767(short int短整型的最大值)。如果需要更多进程,系统管理员可以通过修改/proc/sys/kernel/pid_max来提高上限。<br />    2、内核把进程存放在叫做任务队列(task list)的双向循环链表中。链表中的每一项都是类型为

打滚dě土豆 专栏 613

读书笔记】《Linux内核设计实现进程管理进程调度

大学跟老师做嵌入式项目,写过I2C的设备驱动,但对Linux内核的了解也仅限于此。Android系统许多导致root的漏洞都是内核中的,研究起来很有趣,但看相关的分析文章总感觉隔着一层窗户纸,不能完全理会。所以打算系统的学习一下Linux内核。买了两本书《Linux内核设计实现(第3版)》和《深入理解Linux内核(第3版)》 0x00 一些废话 面向对象思想。 Linux...

weixin_30410999的博客 142
上一篇: Redis源码剖析——dict的实现
下一篇: Redis源码剖析——intset的实现
Coding_Reading
博客等级 码龄10年 17粉丝 66原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值