Linux内核的进程管理 博客分类: Linux c/c++Linux kernel linux多进程系统编程内核
1,进程的概念
进程就是处于执行期的程序,包括代码段,打开的文件,挂起的信号,内核内部数据,处理器状态,内存地址空间,一个或多个执行线程,数据段等。Linux内核对进程和线程并不做特殊的区分。
内核把进程也叫做任务,进程描述符的数据结构类型为task_struct,在32位机器上有1.7KB。
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
struct thread_info *thread_info;
unsigned long flags; /* per process flags, defined below */
unsigned long ptrace;
int prio, static_prio;
struct list_head run_list;
prio_array_t *array;
struct mm_struct *mm, *active_mm;
/* task state */
struct linux_binfmt *binfmt;
pid_t pid;
pid_t tgid;
struct task_struct *real_parent; /* real parent process (when being debugged) */
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 */
struct task_struct *group_leader; /* threadgroup leader */
struct timespec start_time;
/* process credentials */
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
struct user_struct *user;
unsigned short used_math;
char comm[16];
/* CPU-specific state of this task */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked, real_blocked;
struct sigpending pending;
sigset_t *notifier_mask;
struct backing_dev_info *backing_dev_info;
struct io_context *io_context;
};
进程描述符的state描述当前进程的状态:
TASK_RUNNING:正在执行,或在运行队列中
TASK_INTERRUPTIBLE:可中断的睡眠,阻塞状态;等待某些条件的成立,或接收到信号提前被唤醒。
TASK_UNINTERRUPTIBLE:不可中断的睡眠,不对信号做出响应。
TASK_TRACED:被其他进程跟踪的进程
TASK_STOPPED:停止,通常在接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号。
TASK_ZOMBIE:僵死状态,进程执行结束,父进程还没有发布waitpid()系统调用
TASK_DEAD:最终状态
2,进程创建
fork()通过写时拷贝当前进程创建一个子进程,写时拷贝是一种推迟甚至免除拷贝数据的技术,内核并不复制整个进程地址空间,而是让父进程与子进程共享同一个拷贝,只有在需要写入的时候,数据才会复制,从而使各个进程拥有各自的拷贝。fork()只复制父进程的页表以及给子进程创建唯一的进程描述符,子进程与父进程的区别仅仅在于PID和某些资源和统计量的不同。exec()函数负责读取可执行文件并将其载入地址空间。
从内核的角度说,它没有线程这个概念,Linux把所有的线程当作进程来实现,内核没有特别的算法或定义特别的数据结构来表示线程,线程仅仅视为一个与其他进程共享某些资源的进程,每个线程拥有自己的task_struct,所以在内核看来,它就是一个普通的进程。
创建线程:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)
创建进程:
clone(SIGCHLD, 0)
3,CFS公平调度
CFS的出发点基于一个简单的理念:进程调度的效果应如同系统具备一个理想中的完美多任务处理器,每一个进程将能获得1/N的处理器时间,N是指可运行进程的数量。在任何可测量的周期内,我们给予N个进程中每一个进程同样多的运行时间。
CFS的做法是允许每一个进程运行一段时间,循环轮转,选择运行最少的进程作为下一个运行的进程,而不再采用分配给每一个进程时间片的做法了。CFS在所有可运行的进程总数基础上计算出一个进程应该运行多久,nice值在CFS中被作为进程获得的处理器运行比的权重。不是完善的公平,只是近乎完美,在多进程的环境下,降低了调度延迟带来的不公平性。
CFS虚拟运行时间来记录一个程序运行了多长时间及它还应该运行多久,CFS调度算法的核心就是选择具有最小虚拟运行时间的进程,它使用红黑树来组成可运行进程队列。
4,调度器入口
进程调度的主要入口点是函数schedule(),它是其他内核模块调用进程调度器的入口
5,进程睡眠
进程睡眠有多种原因,无法读取IO更多的数据,无法获取信号量,互斥锁或某个硬件事件。进程把自己标记成休眠状态,从可执行进程的红黑树中移出,放入等待队列,然后调用schedule()函数选择和执行一个其他进程。唤醒的过程刚好相反,进程被置为可执行,从等待队列中移到调度器的红黑树中。休眠通过等待队列进行处理,等待队列是由等待某些事件发生的进程组成的简单链表,内核用wait_queue_head_t来表示等待队列:
struct __wait_queue_head{
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head_t wait_queue_head_t;
等待队列链表的元素类型为wait_queue_t;
struct __wait_queue{
unsigned int flags;
struct task_sruct *task;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
DEFINE_WAIT(wait);
/*初始化一个wait_queue_t类型的变量,并用当前进程的描述符和唤醒函数
autoremove_wake_function()的地址初始化。*/
add_wait_queue(q, &wait); //将wait加入到等待队列q中
while(!condition){
prepare_to_wait(&q, &wait, TASK_INTERRUPTIBLE);
if(signal_pending(current))
/*处理信号*
schedule();
}
6,唤醒
唤醒操作通过函数wake_up()进行,它会唤醒等待队列的所有进程,将进程设置为TASK_RUNNING状态,调用enqueue_task()将此进程放入到红黑树中。
void wake_up(wait_queue_head_t *q)
{
struct list_head *tmp;
wait_queue_t *curr;
list_for_each(tmp, &q->task_list){
curr = list_entry(tmp, wait_queue_t, task_list);
if(curr->func(curr, TASK_INTERRUPTIBLE|TASK_UNINTERRUPTIBLE, 0, NULL) && curr->flags)
break;
}
}