linux系统中进程的组织方式
一个系统中通常可以有数百个数千个进程,相应的也就有这么多的PCB,必须使用一些适当的方式将这些PCB组织起来。进程的组织方式其实大都是数据结构中数据的组织方式。
进程链表:双向循环链表将进程联系起来
struct task_struct
{
struct list_head tasks;
char comm[TASK_COM_LEN];//可执行程序的名字带路径
}
链表的头和尾都是init_task,0号进程的PCB,这个进程永远不会被撤销他被静态分配在内核数据段中。
哈希表:有些情况根据进程的PID导出对应的PCB,顺序扫描效率低下。于是引入哈希表。
linux用一个宏pid_hashfn()将PID转换成表的索引。通过pid_hashfn()可以把进程的PID均匀的散列在哈希表中。给定一个PID寻找其对应的PCB函数
static inline struct task_struct * find_task_by_pid(int pid)
{
struct task_struct *p, **htable = &pidhash[pid_hashfn()];
for(p = *htable; p && p->pid != pid; p = pidhash->next)
return p;
}
pidhash是哈希表,其定义为struct task_struct *pidhash[PIDHASH_SZ];
哈希函数并不能保证PID与表的索引一一对应,因此两个不同的PID散列到相同的索引上引起冲突,采用链地址法来处理冲突的PID。也就是说每一个表项是由冲突的PID组成的双休链表。task_struct 结构中有两个域pidhash_next和pidhash_prev来实现这个链表,同一链中的PID由小向大排列。
就绪队列:把所有可运行状态的进程组成一个双向链表叫做就绪队列
struct task_struct{
struct list_head run_list;
,,,
}
就绪队列的定义及相关操作在/kernel/sched.c文件中
static LIST_HEAD(runqueue_head);//定义就绪队列的头指针为runqueue_head
static inline void add_to_runqueue(struct task_struct *p)
{
list_add_tail(&p->run_list, &runqueue_head);
nr_running++;
}
static inline void move_last_runqueue(struct task_struct *p)
{
list_del(&p->run_list);
list_add_tail(&p->run_list, &runqueue_head);
}
等待队列:深度睡眠和浅度睡眠两种状态的进程位于同一个等待队列上,等待某些事件不能运行。
等待队列的数据结构
struct __wait_queue
{
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void * private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
task_list把处于睡眠状态的进程连接成双向链表。睡眠是暂时的唤醒才是目的,设置了func域,该域指向唤醒函数,用于吧队列中的进程唤醒。flags域为了区分睡眠时的互斥进程(1)和非互斥进程(0)。private域是传递给func函数的参数。
等待队列头
struct __wait_queue_head
{
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
等待队列是由中断处理程序和主要的内核函数修改的,因此必须对其双向链表进行保护以免同时访问,通过lock自旋锁进行同步,task_list域是等待进程链表的头