进程是计算机科学史上成功的概念之一,OS教科书中这样定义:进程是程序执行时的一个实例。从内核观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的实体。Linux使用轻量级进程(LWP)对多线程应用程序提供更好的支持。
进程描述符:task_struct,定义在/linux-2.6.xx/include/linux/sched.h
struct task_struct { /* 进程状态 */ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ /* 进程的基本信息 */ struct thread_info *thread_info; /* 进行的动态优先权和静态优先权 */ int prio, static_prio; /* 进程所在运行队列。每个优先级对应一个运行队列 */ struct list_head run_list; /* 指向当前运行队列的prio_array_t */ prio_array_t *array; /* 进程的平均睡眠时间 */ unsigned long sleep_avg; /** * timestamp-进程最近插入运行队列的时间。或涉及本进程的最近一次进程切换的时间 * last_ran-最近一次替换本进程的进程切换时间。 */ unsigned long long timestamp, last_ran; /** * 进程的调度类型:sched_normal,sched_rr或者sched_fifo */ unsigned long policy; /** * time_slice-在进程的时间片中,还剩余的时钟节拍数。 * first_time_slice-如果进程肯定不会用完其时间片,就把该标志设置为1. * xie.baoyou注:原文如此,应该是表示任务是否是第一次执行。这样,如果是第一次执行,并且在开始运行 * 的第一个时间片内就运行完毕,那么就将剩余的时间片还给父进程。主要是考虑到有进程 * 会大量的动态创建子进程时,而子进程会立即退出这种情况。如果不还给父进程时间片,会对这种进程不公平。 */ unsigned int time_slice, first_time_slice; /** * 通过此链表把所有进程链接到一个双向链表中。 */ struct list_head tasks; /** * mm:指向内存区描述符的指针 */ struct mm_struct *mm, *active_mm; unsigned did_exec:1; /** * 进程PID */ pid_t pid; /** * 线程组领头线程的PID。 */ pid_t tgid; /* * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->parent->pid) */ /** * 指向创建进程的进程的描述符。 * 如果进程的父进程不再存在,就指向进程1的描述符。 * 因此,如果用户运行一个后台进程而且退出了shell,后台进程就会成为init的子进程。 */ struct task_struct *real_parent; /* real parent process (when being debugged) */ /** * 指向进程的当前父进程。这种进程的子进程终止时,必须向父进程发信号。 * 它的值通常与real_parent一致。 * 但偶尔也可以不同。例如:当另一个进程发出监控进程的ptrace系统调用请求时。 */ struct task_struct *parent; /* parent process */ /** * 链表头部。链表指向的所有元素都是进程创建的子进程。 */ struct list_head children; /* list of my children */ /** * 指向兄弟进程链表的下一个元素或前一个元素的指针。 */ struct list_head sibling; /* linkage in my parent's children list */ /** * P所在进程组的领头进程的描述符指针。 */ struct task_struct *group_leader; /* threadgroup leader */ /* PID/PID hash table linkage. */ /** * PID散列表。通过这四个表,可以方便的查找同一线程组的其他线程,同一会话的其他进程等等。 */ struct pid pids[PIDTYPE_MAX]; struct completion *vfork_done; /* for vfork() */ /** * 子进程在用户态的地址。这些用户态地址的值将被设置或者清除。 * 在do_fork时记录这些地址,稍后再设置或者清除它们的值。 */ int __user *set_child_tid; /* CLONE_CHILD_SETTID */ int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */ /** * 进程的实时优先级。 */ unsigned long rt_priority; /** * 以下三对值用于用户态的定时器。当定时器到期时,会向用户态进程发送信号。 * 每一对值分别存放了两个信号之间以节拍为单位的间隔,及定时器的当前值。 */ unsigned long it_real_value, it_real_incr; cputime_t it_virt_value, it_virt_incr; cputime_t it_prof_value, it_prof_incr; /** * 每个进程的动态定时器。用于实现ITIMER_REAL类型的间隔定时器。 * 由settimer系统调用初始化。 */ struct timer_list real_timer; /** * 进程在用户态和内核态下经过的节拍数 */ /* file system info */ /** * 文件系统在查找路径时使用,避免符号链接查找深度过深,导致死循环。 * link_count是__do_follow_link递归调用的层次。 * total_link_count调用__do_follow_link的总次数。 */ int link_count, total_link_count; /* filesystem information */ /** * 与文件系统相关的信息。如当前目录。 */ struct fs_struct *fs; /* open file information */ /** * 指向文件描述符的指针 */ struct files_struct *files; /* namespace */ struct namespace *namespace; /* signal handlers */ /** * 指向进程的信号描述符的指针 */ struct signal_struct *signal; /** * 指向进程的信号处理程序描述符的指针 */ struct sighand_struct *sighand; /** * blocked-被阻塞的信号的掩码 * real_blocked-被阻塞信号的临时掩码(由rt_sigtimedwait系统调用使用) */ sigset_t blocked, real_blocked; /** * 存放私有挂起信号的数据结构 */ struct sigpending pending; /** * 信号处理程序备用堆栈的地址 */ unsigned long sas_ss_sp; /** * 信号处理程序备用堆栈的大小 */ size_t sas_ss_size; /** * 指向一个函数的指针,设备驱动程序使用这个函数阻塞进程的某些信号 */ int (*notifier)(void *priv);};
进程状态:
可运行状态(TASK_RUNNING),
可中断的等待状态(TASK_INTERRUPTIBLE),
不可中断的等待状态(TASK_UNINTERRUPTIBLE),
暂停状态(TASK_STOPPED),
跟踪状态(TASK_TRACED),
僵死状态(TASK_ZOMBIE),
僵死撤销状态(EXIT_CODE).
设置方式:task->state = state; 宏 set_current_state(state); set_task_state(task,state);
进程描述符处理:Linux动态分配连续的8K的空间(两个页框)存放与进程相关的thread_info结构与进程内核态栈,使用联合体thread_info表示
union thread_union{
struct thread_info thread_info;
unsigned long stack[2048];
};
这样内核就很容易从esp的值获得当前在CPU上正在运行进程的thread_info结构的地址,进而获得进程描述符task_struct地址。屏蔽掉esp低13位就可以获得thread_union结构的基地址,这项工作由current_thread_info()函数完成,在asm-i386/thread_info.h中如下
static inline struct thread_info *current_thread_info(void) { struct thread_info *ti; __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1))); return ti; }
这是一段内联汇编,输出部是“=r” (ti)返回thread_info的地址存在寄存器中,输入部是(~(THREAD_SIZE-1))相当于待屏蔽结构体低位置零,指令为esp与输入想与。
runqueue队列与进程优先级数组定义在/linux/kernel/sched.c中,具体如下
/**
* 进程优先级数组。每个CPU对应一个此结构。
*/
struct prio_array {
/**
* 链表中进程描述符的数量。
*/
unsigned int nr_active;
/**
* 优先权数组。当且仅当某个优先权的进程链表不为空时设置相应的位标志。
*/
unsigned long bitmap[BITMAP_SIZE];
/**
* 140个优先权队列的头结点。
*/
struct list_head queue[MAX_PRIO];
};
runqueue队列会在进程调度一章介绍先不详细了解,enqueue_task(struct task_struct *p, prio_array_t *array)将某一进程加入某一优先级队列中。
linux内核利用pid_hash(指针数组类型为hlist_head*)加速从进程pid到进程描述符的转换,利用链表法解决hash冲突,hash函数为hash_long()里面用了2的32次的黄金分割附近的素数(斐波那契数列很神奇)。
等待队列,等待队列头、等待队列元素定义在include/linux/wait.h中,wait_queue_head_t,wait_queue_t,相关操作定义在kernel/wait.c中,默认的唤醒函数default_wake_function中调用了try_to_wake_up来唤醒函数(在sched.c中)。
进程资源限制存放在current->signal->rlim字段,该字段类型为rlimit结构体数组,每个资源限制对应一项,利用getrlimit()、setrlimit()系统调用可以修改资源限制。(/include/resource.h)
内核中的双向链表待更新,记得有人说过,这样的使用才是真正的数据结构。
介绍进程fork、clone、vfork与进程退出实现。