Linux平台进程与线程底层实现详解

来源:互联网 发布:ubuntu搜狗输入法 乱码 编辑:程序博客网 时间:2024/06/05 05:39

LinuxProcess信息保存在struct task_struct,是由slab分配的

在linux 中每一个进程都由task_struct数据结构来定义. task_struct就是我们通常所说的PCB.她是对进程控制的唯一手段也是最有效的手段. 当我们调用fork() 时, 系统会为我们产生一个task_struct结构。然后从父进程,那里继承一些数据,并把新的进程插入到进程树中,以待进行进程管理。因此了解task_struct的结构对于我们理解任务调度(linux中任务和进程是同一概念)的关键。

    在进行剖析task_struct的定义之前,我们先按照我们的理论推一下它的结构:

1、进程状态 ,将纪录进程在等待,运行,或死锁

2、调度信息,由哪个调度函数调度,怎样调度等

3、进程的通讯状况

4、因为要插入进程树,必须有联系父子兄弟的指针,当然是task_struct

5、时间信息,比如计算好执行的时间,以便cpu分配

6、标号 ,决定改进程归属

7、可以读写打开的一些文件信息

8、 进程上下文和内核上下文

9、处理器上下文

10、内存信息

因为每一个PCB都是这样的,只有这些结构,才能满足一个进程的所有要求。打开/include/linux/sched.h可以找到task_struct的定义

struct task_struct {

volatile long state;  //说明了该进程是否可以执行,还是可中断等信息

unsigned long flags;  //Flage是进程号,在调用fork()时给出
int sigpending;    //进程上是否有待处理的信号
mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同

                        //0-0xBFFFFFFF foruser-thead
                        //0-0xFFFFFFFFfor kernel-thread

//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatile long need_resched;

int lock_depth;  //锁深度
long nice;       //进程的基本时间片

//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //
进程内存管理信息
int processor;
//
若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //
指向运行队列的指针
unsigned long sleep_time;  //进程的睡眠时间

//用于将系统中所有的进程连成一个双向循环链表,其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages;       //
指向本地页面      
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt;  
//进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal;     
//父进程终止是向子进程发送的信号
unsigned long personality;
//
Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
int did_exec:1; 
pid_t pid;    //
进程标识符,用来代表一个进程
pid_t pgrp;   //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp;  //进程控制终端所在的组标识
pid_t session;  //进程的会话标识
pid_t tgid;
int leader;     //
表示进程是否为会话主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group;   
//线程链表
struct task_struct *pidhash_next; //用于将进程链入HASH
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;  
//供wait4()使用
struct completion *vfork_done;  //供vfork()使用
unsigned long rt_priority; //
实时优先级,用它计算实时进程调度时的weight

 

//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value

//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据

//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。

//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送

//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种

//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据

//it_virt_incr重置初值。

unsigned longit_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer;   //
指向实时定时器的指针
struct tms times;      //记录进程消耗的时间
unsigned long start_time;  //
进程创建的时间

//记录进程在每个CPU上所消耗的用户态时间和核心态时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
//
内存缺页和交换信息:

//min_flt,maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换

//设备读入的页面数);nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt,cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。

//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt,cnswap;
int swappable:1; //
表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid

//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件

//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //
记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组

//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;

intkeep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS];  
//与进程相关的资源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];   //进程正在运行的可执行文件名
 //文件系统信息
int link_count, total_link_count;

//NULL if no tty进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct *tty;
unsigned int locks;
//
进程间通信信息
struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //
当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct thread_struct thread;
  
//文件系统信息
struct fs_struct *fs;
  
//打开文件信息
struct files_struct *files;
  
//信号处理函数
spinlock_tsigmask_lock;
struct signal_struct *sig; //
信号处理函数
sigset_t blocked;  
//进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending;  //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;

spinlock_t alloc_lock;
void *journal_info;
};



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


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

 

Process State

·        TASK_RUNNING (Running or waiting on a runqueue)

·        TASK_INTERRUPTIBLE (Sleeping/blocked, waiting for somecondition to exit)

·        TASK_UNINTERRUPTILBLE 

·        TASK_ZOMBIETerminated, waiting parent's wait4() systemcall)

·        TASK_STOPPED (This occurs if the task receives theSIGSTOP, SIGTSTP, SIGTTIN, or SIGTTOU signal or if itreceives any signal whileit is being debugged.)

两个函数可以操作

set_task_state(task, state); 

set_current_state(state);

 

ProcessCreation

 

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

·        call dup_task_struct,这个创建一个kernelstack/thread_info/task_struct给新的process

·        检测是否超过了当前user的资源限制;

·        childparent区分开,将processdescriptor的成员清空或设置初始值;

·        childstate被置为TASK_UNINTERRUPTIBLE,来保证child还不被运行;

·        设置childtask_structflags成员,标示是超级用户权限的PF_SUPERPRIV被清空,标示还未调用exec()PF_FORKNOEXEC被置上;

·        通过get_pid()pid成员赋值;

·        根据传给clone()的参数来决定是否赋值open files/filesystem info/signal handlers/process address space/name space

·        分割parent的剩余时间片

·        最后copy_process返回一个指向新child的指针给caller

The LinuxImplementation 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 andblocked
signals.

 

Kernel Threads

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

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

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

 

ProcessTermination

 

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

·        current->flagPF_EXITING置上;

·        调用del_timer_sync()来取消内核timer,当此函数返回时,可以保证没有timer handler在运行以及没有timerqueue

·        如果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 dataprocessnamespacesignal handler的引用计数;如果有引用计数为0,则这个对象没有被任何process使用,于是就将其移除

·        随后,current->exit_code被设置,用于之后parent取得该值

·        之后,调用exit_notify()来发送一个信号给parent,同时将current->state置为TASK_ZOMBIE

·        最后,调用 schedule()将当前进程换出

Removal of theProcess Descriptor

 

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

·        首先,调用free_uid(),用于减少process user的引用计数,linux维持一个per-usercache,来表明当前用户有多少个打开的file和打开的process,当这个引用计数为0的时候,这个cache将会销毁

·        运行unhash_process(),把processpidhashtask list中移除

·        processptraced状态(Debug),则reparents(不懂啥意思)to parent,然后从ptrace list中移除

·        最后,运行put_task_struct()释放包含着process kernelstackthread_info的页,以及由slab对收回有slab分配的task_struct结构体

The Dilemma of the Parentless Task

 

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

 

原创粉丝点击