Linux Kernel 学习笔记 Process Management


struct task_struct;
struct thread_info;


Linux的Process信息保存在struct task_struct中,是由slab分配的

而另一个重要的数据结构是thread_info,是存在每个process堆栈的底部,这样的好处是访问的时候可以少用一个寄存器,thread_info有一个task的成员,指向它所隶属的task_struct

有一个重要的宏current来表明当前的task,在x86平台,current是通过计算当前thread_info的task成员来得到的,宏或者是函数current_thread_info()可以非常方便的从当前process stack中得到当前的thread_info,而其成员task即是当前task即current_thread_info()->task;

Process State

  • TASK_RUNNING (Running or waiting on a runqueue)
  • TASK_INTERRUPTIBLE (Sleeping/blocked, waiting for some condition to exit)
  • TASK_UNINTERRUPTILBLE 
  • TASK_ZOMBIE (Terminated, waiting parent's wait4() system call)
  • TASK_STOPPED (This occurs if the task receives the SIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU signal or if itreceives any signal while it is being debugged.)

两个函数可以操作

set_task_state(task, state); 

set_current_state(state);


Process Creation

 

Linux通过clone()系统调用来实现的fork(), clone() 在内核中调用是的do_fork()函数, 而do_fork()又调用了copy_process()来forking process,copy_process()的流程如下:

  • call dup_task_struct,这个创建一个kernel stack/thread_info/task_struct给新的process;
  • 检测是否超过了当前user的资源限制;
  • 将child和parent区分开,将process descriptor的成员清空或设置初始值;
  • child的state被置为TASK_UNINTERRUPTIBLE,来保证child还不被运行;
  • 设置child的task_struct的flags成员,标示是超级用户权限的PF_SUPERPRIV被清空,标示还未调用exec()的PF_FORKNOEXEC被置上;
  • 通过get_pid()给pid成员赋值;
  • 根据传给clone()的参数来决定是否赋值open files/file system info/signal handlers/process address space/name space
  • 分割parent的剩余时间片
  • 最后copy_process返回一个指向新child的指针给caller

The Linux Implementation of Threads


Linux实现thread的方法比较特别,在linux内核中并没有thread这么一个概念,所有的线程在Linux内核中被看做是标准进程,Linux Kernel并不提供针对线程的调度,取而代之的是,在Linux中线程仅仅是一个与其他进程共享某些特定资源的进程,每个线程有独立的task_struct。


Linux的线程是由带有如下参数的clone()系统调用创建的:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

参数的含义:

CLONE_VM: Parent and child share address space.

CLONE_FS: Parent and child share filesystem information

CLONE_FILES: Parent and child share open files.

CLONE_SIGHAND: Parent and child share signal handlers and blocked
signals.

 

Kernel Threads

Kernel thread可以由如下函数创建:

int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)

这个函数实际上也是由clone()带有一个CLONE_KERNEL参数而创建的

 

Process Termination

 

一个进程的结束,是由调用exit()系统调用来完成的,exit()将在内核中调起do_exit()函数,流程如下:

  • 将current->flag的PF_EXITING置上;
  • 调用del_timer_sync()来取消内核timer,当此函数返回时,可以保证没有timer handler在运行以及没有timer在queue中
  • 如果BSD accounting is enable, 则调用acct_process()来写accounting info
  • 调用__exit_mm()来释放被进程占用的mm_struct,如果没有其他进程(线程)在使用这个内存空间,则deallocate
  • 调用exit_sem().若process有在queue中等待的信号量(sem),则在这里将其dequeue
  • 调用__exit_files(), __exit_fs(), exit_namespace(), exit_sighand()用来减少对文件操作符以及filesystem data,process namespace,signal handler的引用计数;如果有引用计数为0,则这个对象没有被任何process使用,于是就将其移除
  • 随后,current->exit_code被设置,用于之后parent取得该值
  • 之后,调用exit_notify()来发送一个信号给parent,同时将current->state置为TASK_ZOMBIE
  • 最后,调用 schedule()将当前进程换出

Removal of the Process Descriptor


当do_exit()调用之后,task_struct实际上还是存在的,这个重要是要等parent取得有关child process的相关信息,当完成之后,wait4()系统调用将被调起,之后在内核中release_task()函数将被调起,流程如下:

  • 首先,调用free_uid(),用于减少process user的引用计数,linux维持一个per-user的cache,来表明当前用户有多少个打开的file和打开的process,当这个引用计数为0的时候,这个cache将会销毁
  • 运行unhash_process(),把process从pidhash和task list中移除
  • 若process是ptraced状态(Debug),则reparents(不懂啥意思)to parent,然后从ptrace list中移除
  • 最后,运行put_task_struct()释放包含着process kernel stack和thread_info的页,以及由slab对收回有slab分配的task_struct结构体

The Dilemma of the Parentless Task

 

当一个进程没有parent的时候(比如parent比child先退出),child进程会将current thread group中的一个作为自己的parent,或者如果没有的话,将init作为自己的parent

相关内容