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

进程管理


进程概念

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

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

进程结构

进程队列组成双向循环链表,类型为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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值