在Linux内核内,进程是由相当大的一个称为task_struct的结构表示的。此结构包含所有表示此进程所必需的数据redhat linux 下载,再者,还包含了大量的其他数据拿来统计(accounting)和维护与其他进程的关系(父和子)。下边给出了task_struct的一小部份。task_struct坐落./linux/include/linux/sched.h。
structtask_struct{
volatilelongstate;
void*stack;
unsignedintflags;
intprio,static_prio;
structlist_headtasks;
structmm_struct*mm,*active_mm;
pid_tpid;
pid_ttgid;
structtask_struct*real_parent;
charcomm[TASK_COMM_LEN];
structthread_structthread;
structfiles_struct*files;
...
};
在task_struct中,可以看见几个意料之中的项,例如执行的状态、堆栈、一组标志、父进程、执行的线程(可以有好多)以及开放文件。对其做简单申明如下
state变量是一些表明任务状态的比特位。常见的状态有:
1.TASK_RUNNING表示进程正在运行,或是排在运行队列中正要运行
2.TASK_INTERRUPTIBLE表示进程正在休眠
3.TASK_UNINTERRUPTIBLE表示进程正在休眠但不能吵醒
4.TASK_STOPPED表示进程停止
注:这种标志的完整列表可以在./linux/include/linux/sched.h内找到。
flags定义了好多指示符,表明进程是否正在被创建(PF_STARTING)或退出(PF_EXITING),或是进程当前是否在分配显存(PF_MEMALLOC)。
每位进程就会被赋于优先级(称为static_prio),但进程的实际优先级是基于加载以及其他几个诱因动态决定的。优先级值越低,实际的优先级越高。
tasks数组提供了链接列表的能力。它包含一个prev表针(指向前一个任务)和一个next表针(指向下一个任务)。
进程的地址空间由mm和active_mm数组表示。mm代表的是进程的显存描述符永久免费linux服务器,而active_mm则是前一个进程的显存描述符(为改进上下文切换时间的一种优化)。
可执行程序的名称(不包含路径)占用comm(命令)主键。
thread_struct则拿来标示进程的储存状态。此元素依赖于Linux在其上运行的特定构架,在./linux/include/asm-i386/processor.h内有这样的一个反例。在此结构内,可以找到该进程自执行上下文切换后的储存(硬件注册表、程序计数器等)。
在好多情况下,进程都是动态创建并由一个动态分配的task_struct表示。其实init进程例外,它总是存在并由一个静态分配的task_struct表示。
Linux内所有进程的分配有两种方法。第一种方法是通过一个哈希表,由PID值进行哈希估算得到;第二种方法是通过双链循环表。循环表十分适宜于对任务列表进行迭代。因为列表是循环的,没有头或尾;并且因为init_task总是存在,所以可以将其用作继续往前迭代的一个锚点。
任务列表未能从用户空间访问,但该问题很容易解决,方式是以模块方式向内核内插入代码。比如通过如下代码,它会迭代任务列表并会提供有关每位任务的少量信息(name、pid和parent名)。
structtask_struct*task=&init_task;
/*Walkthroughthetasklist,untilwehittheinit_taskagain*/
do{
printk(KERN_INFO"***%s[%d]parent%sn",
task->comm,task->pid,task->parent->comm);
}while((task=next_task(task))!=&init_task);
注意,还可以标示当前正在运行的任务。Linux维护一个称为current的符号,代表的是当前运行的进程(类型是task_struct)。因此可使用如下代码:
printk(KERN_INFO,"Currenttaskis%s[%d]”,current->comm,current->pid);
Linux创建用户空间进程的情况与内核空间进程类似。两者底层机制是一致的,由于终还会依赖于一个名为do_fork的函数来创建新进程。
在创建内核线程时,内核会调用一个名为kernel_thread的函数(参见./linux/arch/i386/kernel/process.c)linux 查看运行进程,此函数执行个别初始化后会调用do_fork。
在用户空间,一个程序会调用fork,这会造成对名为sys_fork的内核函数的系统调用(参见./linux/arch/i386/kernel/process.c)。
do_fork是进程创建的基础。可以在./linux/kernel/fork.c内找到do_fork函数(以及相关函数copy_process)。
do_fork函数首先调用alloc_pidmap,该调用会分配一个新的PID。接出来,do_fork检测调试器是否在跟踪父进程。若果是,在clone_flags内设置CLONE_PTRACE标志以做好执行fork操作的打算。以后do_fork函数都会调用copy_process,向其传递这种标志、堆栈、注册表、父进程以及新分配的PID。
新的进程在copy_process函数内作为父进程的一个副本创建。此函数能执行除启动进程之外的所有操作,启动进程在以后进行处理。copy_process内的第一步是验证CLONE标志以确保那些标志是一致的。若果不一致,都会返回EINVAL错误。接出来,寻问LinuxSecurityModule(LSM)看当前任务是否可以创建一个新任务。
接出来,调用dup_task_struct函数(./linux/kernel/fork.c),这会分配一个新task_struct并将当前进程的描述符复制到其内。在新的线程堆栈设置好后,一些状态信息也会被初始化,但是会将控制返回给copy_process。控制回到copy_process后,不仅其他几个限制和安全检测之外,就会执行一些常规管理,包括在新task_struct上的各类初始化。以后,会调用一系列复制函数来复制此进程的各个方面,例如复制开放文件描述符(copy_files)、复制符号信息(copy_sighand和copy_signal)、复制进程显存(copy_mm)以及终复制线程(copy_thread)。
以后,这个新任务会被指定给一个处理程序,同时对容许执行进程的处理程序进行额外的检测(cpus_allowed)。新进程的优先级从父进程的优先级承继后,执行一小部份额外的常规管理,并且控制也会被返回给do_fork。在此时,新进程存在但仍未运行。do_fork函数通过调用wake_up_new_task来修补此问题。此函数(./linux/kernel/sched.c)初始化个别调度程序的常规管理信息linux 查看运行进程,将新进程放置在运行队列之内,之后将其唤起便于执行。后,一旦返回至do_fork,此PID值即被返回给调用程序,进程完成。