进程是Linux 内核最基本的抽象之一,它是处于执行期的程序,或者说“进程=程序+运行”。 但是进程并不仅局限于一段可执行代码(代体段), 它还包括进程需要的其他资源,例如打开的文件,挂起信号量、内存管理、处理器状态、一个或者多个执行线和和数据段。Linux 内核通常把进程叫作是任务(task), 因此进程控制块(processing control block, PCB) 也被命名为 struct task_struct. 在20世纪60年代设计的分时操作系统进程最开始被称之为job, 后来改名为process
线程被称为轻量级进程,它是操作系统调试的最小单元,通常一个进程可以拥有多个线程。线程和进程的区别在于进程拥有独立的资源空间,而线程则共享进程的资源空间。Linux内核并没有对线程特别对线程有特别的调试算法或定义特别的数据结构来标识线程,线程和进程都使用相同的进程PCB 数据结构。内核里使用clone 方法来创进线程,其工作方式和创建进程fork方式类似,但会确定哪些资源和父进程共享,哪些资源为纯种独享。
操作系统好比是一个人类社会,时时刻刻都有进程被创建或结束。进程自有它的生存之道,进程通常通过fork系统调用来创建一个新的进程,新创建的进程可以通过exec() 函数创建新的地址空间,并载入新的程序。进程结束可以自愿退出或非自愿退出。
init 进程
Linux 内核在启动时会有一个init_task进程,它是系统所有进程的“鼻祖”。 称为0号进程或者idle 进程,当系统没没进程调度时,调度器就会执行idle进程。idle进程在内核启动 start_kernel() 函数时静态创建,所有的核心数据结构都预先静态赋值。init_task进程的task_struct 数据结构通过INIT_TASK 宏来赋值,定义在include/linux/init_task.h文件中。
/*
* INIT_TASK is used to set up the first task table, touch at
* your own risk!. Base=0, limit=0x1fffff (=2MB)
*/
#define INIT_TASK(tsk) \
{ \
.state = 0, \
.stack = init_stack, \
.usage = ATOMIC_INIT(2), \
.flags = PF_KTHREAD, \
.prio = MAX_PRIO-20, \
.static_prio = MAX_PRIO-20, \
.normal_prio = MAX_PRIO-20, \
.policy = SCHED_NORMAL, \
.cpus_allowed = CPU_MASK_ALL, \
.nr_cpus_allowed= NR_CPUS, \
.mm = NULL, \
.active_mm = &init_mm, \
.restart_block = { \
.fn = do_no_restart_syscall, \
}, \
.se = { \
.group_node = LIST_HEAD_INIT(tsk.se.group_node), \
}, \
.rt = { \
.run_list = LIST_HEAD_INIT(tsk.rt.run_list), \
.time_slice = RR_TIMESLICE, \
}, \
.tasks = LIST_HEAD_INIT(tsk.tasks), \
INIT_PUSHABLE_TASKS(tsk) \
INIT_CGROUP_SCHED(tsk) \
.ptraced = LIST_HEAD_INIT(tsk.ptraced), \
.ptrace_entry = LIST_HEAD_INIT(tsk.ptrace_entry), \
.real_parent = &tsk, \
.parent = &tsk, \
.children = LIST_HEAD_INIT(tsk.children), \
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
RCU_POINTER_INITIALIZER(real_cred, &init_cred), \
RCU_POINTER_INITIALIZER(cred, &init_cred), \
.comm = INIT_TASK_COMM, \
.thread = INIT_THREAD, \
.fs = &init_fs, \
.files = &init_files, \
.signal = &init_signals, \
.sighand = &init_sighand, \
.nsproxy = &init_nsproxy, \
.pending = { \
.list = LIST_HEAD_INIT(tsk.pending.list), \
.signal = {{0}}}, \
.blocked = {{0}}, \
.alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock), \
.journal_info = NULL, \
.cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), \
.pi_lock = __RAW_SPIN_LOCK_UNLOCKED(tsk.pi_lock), \
.timer_slack_ns = 50000, /* 50 usec default slack */ \
.pids = { \
[PIDTYPE_PID] = INIT_PID_LINK(PIDTYPE_PID), \
[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID), \
[PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \
}, \
.thread_group = LIST_HEAD_INIT(tsk.thread_group), \
.thread_node = LIST_HEAD_INIT(init_signals.thread_head), \
INIT_IDS \
INIT_PERF_EVENTS(tsk) \
INIT_TRACE_IRQFLAGS \
INIT_LOCKDEP \
INIT_FTRACE_GRAPH \
INIT_TRACE_RECURSION \
INIT_TASK_RCU_PREEMPT(tsk) \
INIT_TASK_RCU_TASKS(tsk) \
INIT_CPUSET_SEQ(tsk) \
INIT_RT_MUTEXES(tsk) \
INIT_PREV_CPUTIME(tsk) \
INIT_VTIME(tsk) \
INIT_NUMA_BALANCING(tsk) \
INIT_KASAN(tsk) \
}
init_task 进程的task_struct 数据结构中stack 成员指向thread_info 数据结构。通常内核栈大小是8KB, 即两个物理页面的大小,它存放在内核映像文件中data 段中。
fork
在Linux系统中,进程或线程是通过fork , vfork 或clone 等系统调用来建立的。在内核中,这3个系统调用都是通过同一个函数来实现,即do_fork() 函数,该函数定义在fork.c文件中。
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
do_fork() 函数有5个参数,具体含义如下:
clone_flags: 创建进程标志位集合。
stack_start: 用户态栈的起始地址。
stack_size: 用户态栈的大小,通常调用为0.
parent_tidpr 和 child_tidptr: 指向用户空间中地址的两个指针,分别指向父子进程的PID.