Linux内核-进程

Posted by keys961 on March 18, 2019

1. 进程

进程即处于执行器的程序以及相关资源的总称;线程是在进程中活动的对象(内核调度的对象)。

创建进程fork(),创建子进程,从内核返回2次(父进程&子进程),新进程可接着调用exec()创建新的地址空间(fork()使用COW)

终结进程exit()释放资源,父进程可调用wait4()查看子进程是否终结(终结的进程处于僵死状态,直到父进程调用wait()/waitpid()

2. 进程描述符和任务结构

进程描述符struct task_struct,定义在<linux/shed.h>

2.1. 进程描述符的分配与存放

使用slab分配器分配,达到对象复用和缓存着色。

x86下,获取到task_struct需要通过thread_info的成员访问,thread_info存在内核栈尾端:

1
2
3
4
struct thread_info {
    struct task_struct *task;
    //...
};

访问thread_info通过current_thread_info()函数访问,只要屏蔽栈指针的几个有效位即可,如8KB的栈空间屏蔽后13位。

current宏可直接获取当前进程的task_struct,实际上调用current_thread_info()->task

current宏在进程上下文中是有效的,而在中断上下文中是无效的(因为中断程序不会有其它进程干扰)

2.2. 进程状态

  • TASK_RUNNING:进程可执行,可能在运行队列准备执行,也可能正在执行(OS课本里的Ready & Running状态)
  • TASK_INTERRUPTABLE&TASK_UNINTERRUPTABLE:进程被阻塞,前者可被外部条件中断而投入运行,后者不会(OS课本里Waiting状态)
  • __TASK_TRACED:正在被其它进程跟踪,如调试时
  • __TASK_STOPPED:进程停止运行,不能再次投入运行,通常接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU时发生(OS课本里Stopped状态)

设置状态set_task_state(task, state)/set_current_state(state),定义在<linux/sched.h>

2.3. 进程树

最初进程init进程,PID为1,所有的其它进程都是它的后代

父进程:可通过task_structparent访问

子进程:可通过task_structchildren访问该链表

兄弟进程:拥有同一个父进程

3. 进程创建

使用fork()exec()。前者通过拷贝当前进程创建一个子进程(区别仅在于PID以及某些资源和统计量),后者将可执行文件载入到地址空间开始运行。

exec()exec()一类函数,有多种实现

3.1. 写时拷贝

Linux的fork()使用写时拷贝,初始时父进程和子进程共享一份拷贝,只有需要写入时,数据才会被复制。

3.2. fork()

Linux使用clone()系统调用实现fork(),然后clone()回调用do_fork()创建子进程。通常内核有意选择子进程先执行(若父进程先执行,可能会向地址空间写入)。

父子进程的资源共享可以通过参数调配(如共享打开文件等等)

3.3. vfork()

父进程页表项不会做任何拷贝,其它都相同。

子进程共享父进程的地址空间,且父进程被阻塞,除非子进程退出或执行exec()。此外,子进程不能像地址空间写入。

4. 线程在Linux下的实现

Linux把所有线程当作进程看待,线程也有task_struct,只是线程之间共享某些资源(如地址空间)。

如一个进程拥有4个线程,Linux仅创建4个进程并分配4个普通的task_struct,然后它们指向某些共享的资源(如地址空间)。

4.1. 创建线程

与进程类似,调用clone(),但需要指明共享的资源。

4.2. 内核线程

执行内核的后台操作。它独立运行在内核空间没有独立的地址空间不切换到用户空间,可被调度和抢占。

5. 进程终结

发生在进程调用exit(),最后大部分要靠do_exit()(定义在kernel/exit.c),释放该进程相关联的所有资源,然后进入EXIT_ZOMBIE状态。

它依旧占有内核栈,thread_infotask_struct,目的是向父进程提供信息,父进程检索或者通知内核,认为这是无关信息后,释放进程的剩余内存,归还给系统。

5.1. 删除进程描述符

父进程得到已终结的子进程信息后,或通知内核这些信息无用后,子进程的task_struct才会被释放。

wait()一族函数可知道子进程是否退出,它通过wait4()实现,动作是:挂起进程,等待其中一个子进程退出,然后返回该子进程的PID

最终释放进程描述符时,调用release_task()

5.2. 孤儿进程

父进程提前退出,需要给子进程找一个线程作为父亲(在当前线程组内),若不行,则用init做父亲(否则子进程会僵死,且驻留着一直占用内存)。

init会例行调用wait()检查子进程状态,清除所有僵死进程