操作系统:linux
处理器:arm
内核版本:4.x
目录:
进程
我们日常生活中,手机上会安装各种APP,比如微信、QQ等程序。当系统执行它时,需要很多资源,除了程序代码,还包括于各种依赖文件、内存、处理器等。程序代码运行后,包含在系统中使用到的一些资源,就被当成系统中的一个进程。
在终端敲入ps aux命令可以看到当前的进程。
线程
系统中进程是对资源的抽象,而实际任务调度的最小单元是线程。在linux系统上,进程和线程并没有特殊区分。线程即是一种特殊的进程:当他们使用的系统资源大部分都是共享时,就可以当成线程。
使用线程的好处有很多:比如减少系统在不同任务间切换带来的开销(在同一个程序中),提高系统的运行效率等。
可以使用C库中的clone、pthread_create等API,在用户空间创建线程。
终端中ps命令加上-T选项就可以看到系统中的线程。
可以看到,几个线程他们的pid号都相等,这是为了遵循POSIX标准,但是内核可以用SPID(tgid)把他们区分开来。
pid_t pid;
pid_t tgid;
每个task都包含这两个变量。pid既然是一个变量,那么肯定有数量限制,可以通过读取proc节点获取当前最大pid值(cat /proc/sys/kernel/pid_max)。
后续文章为了行文方便,就不细分进程线程。
task_struct
在linux系统中,使用task_struct来描述进程所拥有的资源,他有几KB的大小。
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
……
……
……
#endif
#ifdef CONFIG_LIVEPATCH
int patch_state;
#endif
#ifdef CONFIG_SECURITY
/* Used by LSM modules for access restriction: */
void *security;
#endif
/*
* New fields for task_struct should be added above here, so that
* they are included in the randomized portion of task_struct.
*/
randomized_struct_fields_end
/* CPU-specific state of this task: */
struct thread_struct thread;
/*
* WARNING: on x86, 'thread_struct' contains a variable-sized
* structure. It *MUST* be at the end of 'task_struct'.
*
* Do not put anything below here!
*/
};
这个结构体中存放的信息很多:进程状态、进程信息、内核栈、进程调度、进程地址空问、进程ID、组管理、用户管理、工作目录、根目录、文件描述符、信号信息、信号处理程序等。
每个进程都会有一个内核栈来存储信息,arm平台一般32位芯片内核栈大小为8KB,64位芯片内核栈大小为16KB。在2.6以前的内核,task_struct直接存储在内核栈的底部。
thread_info
在2.6版本之后,出于性能和资源的考虑,描述进程信息的task_struct使用内存接口动态分配(slab分配器)。而内核栈中保留thread_info,thread_info仍然固定存放在内核栈的底部。SP寄存器的值经过位操作,就可以得到thread_info地址,thread_info中存放了task_struct的指针,这样就可以找到对应的进程信息。
上图很好展现了arm平台sp、thread_info、task_struct之间的关系。
在arm64平台上内核开发者利用了硬件的新特性,因为用户态进入内核态时,寄存器会被保存在pt_regs数组中(存放在内核栈顶部),用户态的sp指针暂时不会使用,所以利用他来存放当前task_struct的位置。这使得不再需要用thread_info来找到当前task_struct。
static __always_inline struct task_struct *get_current(void)
{
unsigned long sp_el0;
asm ("mrs %0, sp_el0" : "=r" (sp_el0));
return (struct task_struct *)sp_el0;
}
sp_el0即用户态的sp寄存器,get_current直接利用他来得到当前的task_struct地址。
因为这个改变,thread_info也被大大删减。
ARM32平台:
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
};
ARM64平台:
struct thread_info {
unsigned long flags; /* low level flags */
mm_segment_t addr_limit; /* address limit */
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
u64 ttbr0; /* saved TTBR0_EL1 */
#endif
int preempt_count; /* 0 => preemptable, <0 => bug */
};