进程
进程是Linux内核最基本的抽象之一,它是处于执行期的程序,或者说“进程=程序+执行”。但是进程并不仅局限于一段可执行代码(代码段),它还包括进程需要的其他资源,例如打开的文件、挂起的信号量、内存管理、处理器状态、一个或者多个执行线程和数据段等。Linux内核通常把进程叫作是任务(task),因此进程控制块(processing control block,PCB)也被命名为struct task_struct。在20世纪60年代设计的分时操作系统进程最开始被称为工作(job),后来改名为进程(process)。
线程被称为轻量级进程,它是操作系统调度的最小单元,通常一个进程可以拥有多个线程。线程和进程的区别在于进程拥有独立的资源空间,而线程则共享进程的资源空间。Linux内核并没有对线程有特别酌调度算法或定义特别的数据结构来标识线程,线程和进程都使用相同的进程PCB数据结构。内核里使用clone方法来创建线程,其工作方式和创建
进程fork方法类似,但会确定哪些资源和父进程共享,哪些资源为线程独享。
操作系统好比是一个人类社会,时时刻刻都有进程被创建或结束。进程自有它的生存之道,进程通常通过fork系统调用来创新一个新的进程,新创建的进程可以通过exec()函数创建新的地址空间,并载入新的程序。进程结束可以自愿退出或非自愿退出。
本章主要讲述fork系统调用的实现。fork系统调用是所有进程的孵化器(idle进程除外),因此本节重点讲解进程是如何被孵化出来的。fork的实现会涉及到进程管理、内存管理、文件系统和信号处理等内容,本章会讲述一些核心的实现过程。
init进程
Linux内核在启动时会有一个init_task进程,它是系统所有进程的“鼻祖”,称为0号进程或idle进程,当系统没有进程需要调度时,调度器就会去执行idle进程。idle进程在内核启动( start_kemel()函数)时静态创建,所有的核心数据结构都预先静态赋值。init_task进程的task struct数据结构通过INIT_TASK宏来贼值,定义在include/linux/init_task.h文件中。
init/init_task.c
/* Initial task structure */
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);
include/linux/sched.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_thread_info, \
.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, \
......
}
init task进程的task struct数据结构中stack成员指向thread info数据结构。通常内核栈大小是8KB,即两个物理页面的大小,它存放在内核映像文件中data段中,在编译链接时预先分配好,具体见arch/arm/kernel/vmlinux.lds.S链接文件。
arch/arm/kernel/vmlinux.lds.S
SECTIONS
{
......
.data : AT(__data_loc) {
_data = .; /* address in memory */
_sdata = .;
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
INIT_TASK_DATA(THREAD_SIZE)
#ifdef CONFIG_XIP_KERNEL
. = ALIGN(PAGE_SIZE);
__init_begin = .;
INIT_DATA
ARM_EXIT_KEEP(EXIT_DATA)
. = ALIGN(PAGE_SIZE);
__init_end = .;
#endif
NOSAVE_DATA
CACHELINE_ALIGNED_DATA(L1_CACHE_BYTES)
READ_MOSTLY_DATA(L1_CACHE_BYTES)
/*
* and the usual data section
*/
DATA_DATA
CONSTRUCTORS
_edata = .;
}
......
}
arch/arm/include/asm/thread_info.h
#define THREAD_SIZE_ORDER 1
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
#define THREAD_START_SP (THREAD_SIZE - 8)
include/asm-generic/vmlinux.lds.h
#define INIT_TASK_DATA(align) \
. = ALIGN(align); \
*(.data..init_task)
由链接文件可以看到data段预留了8KB的空间用于内核栈,存放在data段的“.data…init_task”中。__init_task_data宏会直接读取“.data…init_task”段内存,并且存放了一个thread_union联合数据结构,从联合数据结构可以看出其分布情况:开始的地方存放了struct thread_info数据结构,顶部往下的空间用于内核栈空间。
include/linux/init_task.h
/* Attach to the init_task data structure for proper alignment */
#define __init_task_data __attribute__((__section__(".data..init_task")))
init/init_task.c
/*
* Initial thread structure. Alignment of this is handled by a special
* linker map entry.
*/
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
include/linux/sched.h
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
arch/arm/include/asm/thread_info.h
#define INIT_THREAD_INFO(tsk) \
{ \
.task = &tsk, \
.flags = 0, \
.preempt_count = INIT_PREEMPT_COUNT, \
.addr_limit = KERNEL_DS, \
.cpu_domain = domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT), \
}
_init task_data存放在”.data…init_task”段中,_init task_data声明为thread _union类型,thread_union类型描述了整个内核栈stack[],栈的最下面存放struct thread_info数据结构,因此__init_task_data也通过INIT_THREAD_INFO宏来初始化struct thread_info教据结构。init进程的task_struct数据结构通过INIT_TASK宏来初始化。
ARM32处理器从汇编代码跳转到C语言的入口点在start_kernel()函数之前,设置了SP寄存器指向8KB内核栈顶部区域(要预留8Byte的空洞)。
arch/arm/kernel/head-common.S
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
adr r3, __mmap_switched_data
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
cmp r7, #0
strne r0, [r7] @ Save control register values
b start_kernel
ENDPROC(__mmap_switched)
.align 2
.type __mmap_switched_data, %object
__mmap_switched_data:
.long __data_loc @ r4
.long _sdata @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
#ifdef CONFIG_CPU_CP15
.long cr_alignment @ r7
#else
.long 0 @ r7
#endif
.long init_thread_union + THREAD_START_SP @ sp
.size __mmap_switched_data, . - __mmap_switched_data
arch/arm/include/asm/thread_info.h
#define THREAD_START_SP (THREAD_SIZE - 8)
在汇编代码mmap_switched标签处设置相关的r3~r7以及SP寄存器,其中,SP寄存器指向data段预留的8KB空间的顶部(8KB -8),然后跳转到start_kernel()。__mmap_switched_data标签处定义了r4~sp寄存器的值,相当于一个表,通过adr指令把这表读取到r3寄存器中,然后再通过ldmia指令写入相应寄存器中。
内核有一个常用的常量current用于获取当前进程task_struct数据结构,它利用了内核栈的特性。首先通过SP寄存器获取当前内核栈的地址,对齐后可以获取struct thread_info数据结构指针,最后通过thread_info->task成员获取Linux内核栈的结构图。
include/asm-generic/current.h
#define get_current() (current_thread_info()->task)
#define current get_current()
arch/arm/include/asm/thread_info.h
/*
* how to get the current stack pointer in C
*/
register unsigned long current_stack_pointer asm ("sp");
/*
* how to get the thread information struct from C
*/
static inline struct thread_info *current_thread_info(void) __attribute_const__;
static inline struct thread_info *current_thread_info(void)
{
return (struct thread_info *)
(current_stack_pointer & ~(THREAD_SIZE - 1));
}
struct thread_info数据结构定义如下:
arch/arm/include/asm/thread_info.h
/*
* low level task data that entry.S needs immediate access to.
* __switch_to() assumes cpu_context follows immediately after cpu_domain.
*/
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value[2]; /* TLS registers */
#ifdef CONFIG_CRUNCH
struct crunch_state crunchstate;
#endif
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
};