进程
进程是处于执行期的程序。
进程还要包括其它资源,如打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程,还包括用来存放全局变量的数据段等。
所以,进程是处于执行期的程序以及相关资源的总称。
进程的另一个名字是任务(task)。
线程
线程是进程中的活动对象。
每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。
Linux系统对线程和进程并不特别区分。
Linux把所有的线程都当成进程来实现。
进程仅仅被视为一个与其它进程共享某些资源的进程。
进程相关的系统调用
系统调用的实现中增加了sys_x,x是系统调用名
fork():(arch\x86\kernel\process.c)
int sys_fork(struct pt_regs *regs)
{
return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
}
通过复制一个现有的进程来创建一个全新的进程。
fork()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。
clone():(arch\x86\kernel\process.c)
long
sys_clone(unsigned long clone_flags, unsigned long newsp,
void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
if (!newsp)
newsp = regs->sp;
return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}
书中说Linux通过clone()系统调用实现fork(),但是实际上并不是。
两者都通过do_fork()来实现。
do_fork()完成进程创建的大部分工作,它位于kernel\fork.c。
exec:(arch\x86\kernel\process.c)
long sys_execve(char __user *name, char __user * __user *argv,
char __user * __user *envp, struct pt_regs *regs)
{
long error;
char *filename;
filename = getname(name);
error = PTR_ERR(filename);
if (IS_ERR(filename))
return error;
error = do_execve(filename, argv, envp, regs);
#ifdef CONFIG_X86_32
if (error == 0) {
/* Make sure we don't return using sysenter.. */
set_thread_flag(TIF_IRET);
}
#endif
putname(filename);
return error;
}
exec创建新的地址空间,并把新的程序载入其中。
exec实际上有一组函数,而不是一个。
内核系统调用有execve()、execlp()、execle()、execv()和execvp()。
exit():(kernel\exit.c)
SYSCALL_DEFINE1(exit, int, error_code)
{
do_exit((error_code&0xff)<<8);
}
程序退出执行。
进程退出执行后被设置成僵死状态,直到它的父进程调用wait()或waitpid()为止。
wait4():(kernel\exit.c)
SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
int, options, struct rusage __user *, ru)
{
struct wait_opts wo;
struct pid *pid = NULL;
enum pid_type type;
long ret;
if (options & ~(WNOHANG|WUNTRACED|WCONTINUED|
__WNOTHREAD|__WCLONE|__WALL))
return -EINVAL;
if (upid == -1)
type = PIDTYPE_MAX;
else if (upid < 0) {
type = PIDTYPE_PGID;
pid = find_get_pid(-upid);
} else if (upid == 0) {
type = PIDTYPE_PGID;
pid = get_task_pid(current, PIDTYPE_PGID);
} else /* upid > 0 */ {
type = PIDTYPE_PID;
pid = find_get_pid(upid);
}
wo.wo_type = type;
wo.wo_pid = pid;
wo.wo_flags = options | WEXITED;
wo.wo_info = NULL;
wo.wo_stat = stat_addr;
wo.wo_rusage = ru;
ret = do_wait(&wo);
put_pid(pid);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(4, ret, upid, stat_addr, options, ru);
return ret;
}
父进程可以调用wait4()查询子进程是否终结。
内核负责实现wait4()系统调用。Linux通过C库通常需要提供wait()、waitpid()、wait3()和wait4()函数。
init进程会例行调用wati()来检查其子进程,清除所有与其相关的僵死进程。
进程描述符
内核中大部分处理进程的代码都是直接通过task_struct进行的。
内核把进程的列表放在叫做任务队列的双向循环链表中,链表中的每一项是类型为task_struct,称为进程描述符的结构。
进程描述符中包含一个具体进程的所有信息。
进程描述符通过另外一个结构体thread_info结构体访问(arch\x86\include\asm\thread_info.h:):
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable,
<0 => BUG */
mm_segment_t addr_limit;
struct restart_block restart_block;
void __user *sysenter_return;
#ifdef CONFIG_X86_32
unsigned long previous_esp; /* ESP of the previous stack in
case of nested (IRQ) stacks
*/
__u8 supervisor_stack[0];
#endif
int uaccess_err;
};
在创建进程时会创建两个栈,其中一个就是内核栈。
在栈底(对于向下增长的栈来说)或栈顶(对于向上增长的栈来说)会创建这个thread_info结构体。
它的成员task就指向了进程的task_struct结构体。
Linux中定义了一个宏(arch\x86\include\asm\current.h)可以直接访问当前进程:
#ifndef _ASM_X86_CURRENT_H
#define _ASM_X86_CURRENT_H
#include <linux/compiler.h>
#include <asm/percpu.h>
#ifndef __ASSEMBLY__
struct task_struct;
DECLARE_PER_CPU(struct task_struct *, current_task);
static __always_inline struct task_struct *get_current(void)
{
return percpu_read_stable(current_task);
}
#define current get_current()
#endif /* __ASSEMBLY__ */
#endif /* _ASM_X86_CURRENT_H */
进程状态
如图所示:
TASK_RUNNING:进程可执行的:它或者正在执行,或者在运行队列中等待执行。
TASK_INTERRUPTIBLE:进程正在睡眠中(也就是说他被阻塞),等待某些条件的达成。
TASK_UNINTERRUPTIBLE:除了就算是接受到信号也不会被唤醒或准备投入运行之外,这个状态与TASK_INTERRUPTIBLE状态相同。
__TASK_TRACED:被其他进程跟踪的进程。
__TASK_STOPPED:进程停止执行。通常发生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号的时候。
对应的代码:
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* about the task exiting. Confusing, but this way
* modifying one set can't modify the other one by
* mistake.
*/
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
内核线程
内核线程是独立运行在内核空间的标准进程。
内核线程没有独立的地址空间。
内核进程和普通进程一样,可以被调度,也可以被抢占。