所谓进程就是程序执行时的一个实例. 它是现代操作系统中一个很重要的抽象,我们从进程的生命周期:创建,执行,消亡来分析一下Linux上的进程管理实现.
一:前言
进程管理结构;
在内核中,每一个进程对应一个task.就是以前所讲的PCB.它的结构如下(include/linux/sched.h):
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
……
……
}
由于这个结构包含了进程的所有信息,所以十分庞大,我们在以后的分析中再来分析各成员的含义。
Task_struct的存放:
在系统运行过程中,进程切换十分频繁,所以我们需要一种方式能够快速获得当前进程的task_struct。linux的task_struct存放如下图所示:
如上图所示:进程内核堆栈底部存放着struct thread_struct.该结构中有一个成员指向当前进程的task_struct.在内核中有一个获取当前进程的thread_struct 的宏。它的定义如下:
#define GET_THREAD_INFO(reg)
movl $THREAD, reg;
andl %esp, reg
THREAD_SIZE定义如下:
#ifdef CONFIG_4KSTACKS
#define THREAD_SIZE (4096)
#else
#define THREAD_SIZE (8192)
#endif
我们讨论常规的8K栈的情况。-THREAD_SIZE即为:0xFFFFE000.因为栈本身是页面对齐的.所以只要把低13位屏弊掉就是thread_struct.的地址.
进程链表:
每一个进程都有父进程,相应的每个进程都会管理自己的子进程.在linux系统中,所有进程都是由init进程派生而来.init进程的进程描述符由init_task静态生成.它的定义如下所示:
struct task_struct init_task = INIT_TASK(init_task);
#define INIT_TASK(tsk)
{
.state = 0,
.stack = &init_thread_info,
.usage = ATOMIC_INIT(2),
……
……
.dirties = INIT_PROP_LOCAL_SINGLE(dirties),
INIT_TRACE_IRQFLAGS
INIT_LOCKDEP
}
每个进程都有一个parent指向它的父进程,都有一个children指针指向它的子进程.上面代码将init进程描述符的parent指针指向其本身.children指针为一个初始化的空链表.
综上所述,我们只要从init_task的children链表中遍历,就可以找到系统中所有的用户进程.这是由do_each_thread宏实现的.代码如下所示:
#define do_each_thread(g, t)
for (g = t = &init_task ; (g = t = next_task(g)) != &init_task ; ) do
next_task定义如下所示:
#define next_task(p) list_entry(rcu_dereference((p)->tasks.next), struct task_struct, tasks)
不过,用这种方法去寻找一个进程太浪费时间了.所以在根据条件寻找进程的话一般使用哈希表
二:创建进程
在用户空间创建进程的接口为:fork(),vfork(),clone()接下来我们看下在linux内核中是如何处理这些请求的.
上述几个接口在经过系统调用进入内核,在内核中的相应处理函数为:sys_fork().sys_vfork().sys_clone()/如下所示:
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
asmlinkage int sys_clone(struct pt_regs regs)
{
unsigned long clone_flags;
unsigned long newsp;
int __user *parent_tidptr, *child_tidptr;
clone_flags = regs.ebx;
newsp = regs.ecx;
parent_tidptr = (int __user *)regs.edx;
child_tidptr = (int __user *)regs.edi;
if (!newsp)
newsp = regs.esp;
return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
从上面可以看出几种调用都会进入同一个接口:do_fork.不同的时,所带的标志不同/标志的含义如下:
#define SIGCHLD 17
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
从上可以看出.最低的两位通常表示信号位,即子进程终止的时候应该向父进程发送的信号.一般为SIGCHLD
其余的位是共享位. 设置CLONE_VM时,子进程会跟父进程共享VM区域. CLONE_VFORK标志设置时.子进程运行时会使父进程投入睡眠,直到子进程不再使用父进程的内存或者子进程退出去才会将父进程唤醒.这样做是因为父子进程共享同一个地址区域,所以,创建进程完后,子进程退出,父进程找不到自己的返回地址.
Clone会设置自己的标志,并且可以指定自己的栈的地址/
转入到do_fork():
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
//分配一个新的pid
struct pid *pid = alloc_pid();
long nr;
if (!pid)
return -EAGAIN;
nr = pid->nr;
//如果当前进程被跟踪,子进程如果设置了相关被跟踪标志,则设置CLONE_PTRACE位
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
//copy父进程的一些信息
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
if (!IS_ERR(p)) {
struct completion vfork;
//如果带有CLONE_VFORK标志.赋值并初始化vfork_done
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
//如果进子进程被跟踪,或者子进程初始化成STOP状态
//则发送SIGSTOP信号.由于子进程现在还没有运行,信号不能被处理
//所以设置TIF_SIGPENDING标志
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
//如果子进程末定义CLONE_STOPPED标志,将其置为RUNNING.等待下一次调度
//否则将子进程状态更改为TASK_STOPPED
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
p->state = TASK_STOPPED;
//如果子进程被定义,通发送通告
if (unlikely (trace)) {
current->ptrace_message = nr;
ptrace_notify ((trace << 8) | SIGTRAP);
}
//如果定义了CLONE_VFORK标志.则将当前进程投入睡眠
if (clone_flags & CLONE_VFORK) {
freezer_do_not_count();
wait_for_completion(&vfork);
freezer_count();
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
current->ptrace_message = nr;
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
}
}
} else {
//如果copy父进程相关信息失败了.释放分配的pid
free_pid(pid);
nr = PTR_ERR(p);
}
return nr;
}
我们在开始的时候分析过VFORK标志的作用,在这里我们注意一下VFORK标志的处理:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
……
……
/*
static inline void init_completion(struct completion *x)
{
//done标志为0。表示子进程还没有将父进程唤醒
x->done = 0;
//初始化一个等待队列
init_waitqueue_head(&x->wait);
}
*/
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
……
……
//如果定义了CLONE_VFORK标志.则将当前进程投入睡眠
if (clone_flags & CLONE_VFORK) {
freezer_do_not_count();
wait_for_completion(&vfork);
freezer_count();
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
current->ptrace_message = nr;
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
}
}
……
}
跟踪一下wait_for_completion():
void fastcall __sched wait_for_completion(struct completion *x)
{
might_sleep();
spin_lock_irq(&x->wait.lock);
if (!x->done) {
//初始化一个等待队列
DECLARE_WAITQUEUE(wait, current);
wait.flags |= WQ_FLAG_EXCLUSIVE;
//将其加入到子进程的等待队列
__add_wait_queue_tail(&x->wait, &wait);
do {
//设置进程状态为TASK_UNINTERRUPTIBLE
__set_current_state(TASK_UNINTERRUPTIBLE);
spin_unlock_irq(&x->wait.lock);
//重新调度
//一般来说,在这里的时候就会退出当前进程,去调度另外的进程,直到被子进程唤醒
schedule();
spin_lock_irq(&x->wait.lock);
} while (!x->done); //一直到x->done标志被设置。这里是为了防止异常情况将进程唤醒
//从等待队列中移除
__remove_wait_queue(&x->wait, &wait);
}
x->done--;
spin_unlock_irq(&x->wait.lock);
}
接着分析do_fork(),copy_proces()是它的核心函数。重点分析一下:
static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
struct pid *pid)
{
int retval;
struct task_struct *p = NULL;
//clone_flags参数的有效性判断
//不能同时定义CLONE_NEWNS,CLONE_FS
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
//如果定义CLONE_THREAD,则必须要定义CLONE_SIGHAND
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
//如果定义CLONE_SIGHAND,则必须要定义CLONE_VM
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
retval = security_task_create(clone_flags);
if (retval)
goto fork_out;
retval = -ENOMEM;
//从父进程中复制出一个task
p = dup_task_struct(current);
if (!p)
goto fork_out;
rt_mutex_init_task(p);
#ifdef CONFIG_TRACE_IRQFLAGS
DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif
retval = -EAGAIN;
//如果用户的进程总数超过了限制
if (atomic_read(&p->user->processes) >=
p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->user != current->nsproxy->user_ns->root_user)
goto bad_fork_free;
}
//更新进程用户的相关计数
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes);
get_group_info(p->group_info);
//当前进程数是否大于系统规定的最大进程数
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
//加载进程的相关执行模块
if (!try_module_get(task_thread_info(p)->exec_domain->module))
goto bad_fork_cleanup_count;
if (p->binfmt && !try_module_get(p->binfmt->module))
goto bad_fork_cleanup_put_domain;
//子进程还在进行初始化,没有execve
p->did_exec = 0;
delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
//copy父进程的所有标志,除了PF_SUPERPRIV(超级权限)
//置子进程的PF_FORKNOEXEC标志,表示正在被FORK
copy_flags(clone_flags, p);
//赋值子进程的pid
p->pid = pid_nr(pid);
retval = -EFAULT;
if (clone_flags & CLONE_PARENT_SETTID)
if (put_user(p->pid, parent_tidptr))
goto bad_fork_cleanup_delays_binfmt;
//初始化子进程的几个链表
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
p->vfork_done = NULL;
spin_lock_init(&p->alloc_lock);
//父进程的TIF_SIGPENDING被复制进了子进程,这个标志表示有末处理的信号
//这个标志子进程是不需要的
clear_tsk_thread_flag(p, TIF_SIGPENDING);
init_sigpending(&p->pending);
//初始化子进程的time
p->utime = cputime_zero;
p->stime = cputime_zero;
p->prev_utime = cputime_zero;
……
……
//tgid = pid
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid;
//copy父进程的其它资源.比例打开的文件,信号,VM等等
if ((retval = security_task_alloc(p)))
goto bad_fork_cleanup_policy;
if ((retval = audit_alloc(p)))
goto bad_fork_cleanup_security;
/* copy all the process information */
if ((retval = copy_semundo(clone_flags, p)))
goto bad_fork_cleanup_audit;
if ((retval = copy_files(clone_flags, p)))
goto bad_fork_cleanup_semundo;
if ((retval = copy_fs(clone_flags, p)))
goto bad_fork_cleanup_files;
if ((retval = copy_sighand(clone_flags, p)))
goto bad_fork_cleanup_fs;
if ((retval = copy_signal(clone_flags, p)))
goto bad_fork_cleanup_sighand;
if ((retval = copy_mm(clone_flags, p)))
goto bad_fork_cleanup_signal;
if ((retval = copy_keys(clone_flags, p)))
goto bad_fork_cleanup_mm;
if ((retval = copy_namespaces(clone_flags, p)))
goto bad_fork_cleanup_keys;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_namespaces;
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
/*
* Clear TID on mm_release()?
*/
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;
p->robust_list = NULL;
#ifdef CONFIG_COMPAT
p->compat_robust_list = NULL;
#endif
INIT_LIST_HEAD(&p->pi_state_list);
p->pi_state_cache = NULL;
/*
* sigaltstack should be cleared when sharing the same VM
*/
if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
p->sas_ss_sp = p->sas_ss_size = 0;
/*
* Syscall tracing should be turned off in the child regardless
* of CLONE_PTRACE.
*/
clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
#ifdef TIF_SYSCALL_EMU
clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
#endif
/* Our parent execution domain becomes current domain
These must match for thread signalling to apply */
p->parent_exec_id = p->self_exec_id;
/* ok, now we should be set up.. */
//exit_signal: 子进程退出时给父进程发送的信号
p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
//pdeath_signal:进程退出时.给其下的子进程发送的信号
p->pdeath_signal = 0;
p->exit_state = 0;
……
……
if (likely(p->pid)) {
add_parent(p);
if (unlikely(p->ptrace & PT_PTRACED))
__ptrace_link(p, current->parent);
if (thread_group_leader(p)) {
p->signal->tty = current->signal->tty;
p->signal->pgrp = process_group(current);
set_signal_session(p->signal, process_session(current));
attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
attach_pid(p, PIDTYPE_SID, task_session(current));
list_add_tail_rcu(&p->tasks, &init_task.tasks);
__get_cpu_var(process_counts)++;
}
attach_pid(p, PIDTYPE_PID, pid);
//当前进程数递增
nr_threads++;
}
//被fork的进程数计数递增
total_forks++;
spin_unlock(¤t->sighand->siglock);
write_unlock_irq(&tasklist_lock);
proc_fork_connector(p);
return p;
……
……
}
这个函数比较复杂,里面涉及到了内核的很多子系统,我们暂时只分析与内存相关的部份,其它的子系统待专题分析的时候再讨论。请关注本站更新 ^_^.分析一下里面调用的几个重要的子函数。
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
//保存FPU信息,并设置TS标志
prepare_to_copy(orig);
//分配一个进程描述符
tsk = alloc_task_struct();
if (!tsk)
return NULL;
//分配thread_info
ti = alloc_thread_info(tsk);
if (!ti) {
//如果分配thread_info失败.则释放分配的task
free_task_struct(tsk);
return NULL;
}
//复制task信息
*tsk = *orig;
//使task->stack指向thread_info
tsk->stack = ti;
//copy父进程的thread_info信息
//并使thread_info.task指向task
setup_thread_stack(tsk, orig);
#ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = get_random_int();
#endif
/* One for us, one for whoever does the "release_task()" (usually parent) */
atomic_set(&tsk->usage,2);
atomic_set(&tsk->fs_excl, 0);
#ifdef CONFIG_BLK_DEV_IO_TRACE
tsk->btrace_seq = 0;
#endif
tsk->splice_pipe = NULL;
return tsk;
}
如果进程使用了FPU,MMX,XMM寄存器,就会将进程flag设置TS_USEDFPU标志位。在fork子过程的时候,这几个寄存器的值子进程是不需要的,所以没必要复制到子进程中。为了避免不必要的保存,I386采取了特殊的机制。在CR0中有一个特殊的标志位:TS。当这个标志被设置,如果要访问FPU,MMX,XMM就会产生一个设备通用保护异常。对于父进程来说,它对这几个特殊处理器的处理如下:
如果进程使用了FPU,MMX,XMM寄存器(看父进程是否设置了TS_USEDFPU位),就会将寄存器里的值保存起来,并设置TS标志。
如果父进程以后要使用MMX,XMM,FPU等寄存器,由于TS标志被设置,就产生一个异常,再由异常处理程序从task的相关字段中恢复这几个寄存器的值(如果task相关字段有保存这几个特殊寄存器值的话),或者将这几个寄存器初始化。
上述的这个过程是由prepare_to_copy()进行处理的。具体代码如下:
void prepare_to_copy(struct task_struct *tsk)
{
unlazy_fpu(tsk);
}
Unlazy_fpu() à __unlazy_fpu():
#define __unlazy_fpu( tsk ) do {
//如果使用了MMX,M,FPU寄存器
if (task_thread_info(tsk)->status & TS_USEDFPU) {
//保存相关寄存器
__save_init_fpu(tsk);
//设置TS
stts();
} else
tsk->fpu_counter = 0;
文章出处:DIY部落()