支持线程的计算机系统里面,进程作为资源分配的基本单位而存在,线程作为调度的基本单位而存在.线程仅拥有必不可少的一些资源,如:一组寄存器,堆栈信息等等和其他线程共享同一个进程的所有资源.所以,在同一个进程的线程切换时不需要大量的保存和恢复工作,同时由于共享同一个存储空间,不需要更新快表TLB,提高了系统性能.
1. 线程的实现方法有三种
- 用户级线程;
- 内核级线程;
- 混合线程模型.
2. Linux内核中的进程和线程都用相同的数据结构task_struct表示
线程是特殊的进程,共享同一地址空间,共同合作.进程与进程描述符是一一对应的关系.用数据结构task_struct来表示,(定义在 /Linux/sched.h 中),它包含了进程的详细信息,主要有进程标识符PID,进程占用的内存区域,相关文件的文件描述符,进程环境,信号处理,同步处理等.
进程标识符PID.POSIX标准中规定一个多线程应用程序中的所有线程都必须有相同的PID,在linux内核引入线程机制时,采用了线程组机制,同一线程组中的线程有相同的线程组号(Thread Group ID),tgid.线程组组号放在进程描述符的成员变量tgid中.
3. 进程的状态
进程状态的转移关系如下图:
深度睡眠和浅度睡眠进程得到它需要的资源被唤醒,通过schedule()进入执行态,深度睡眠的进程不能被信号或者定时中断唤醒,只有它申请的资源又有效是才能被唤醒.
4. 进程上下文
在进程切换时,需要保存当前运行进程的执行状态,这些状态信息就是进程上下文.内核设计了一个更小巧的数据结构struct thread_struct 来保存内核使用的相关任务状态段内容,他们在进程描述符的成员变量thread中.
5. 当前进程
Linux2.2内核开始采用宏定义current来获取当前进程的描述符.
内核首先将当前进程的地址及需要快速访问的其他状态标记记录在数据结构struct thread_info中,然后将该数据结构保存到内核态栈栈空间中的最低地址位置.该数据结构在内核态的位置有数据结构union thread_union决定,定义如下:
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
}
这里,内核态栈stack与数据结构thread_info共享同一块内存.由于内核态栈由高地址向地地址方向增长,且内核态栈占的空间比数据结构thread_info大得多,所以有效的防止他们互相覆盖和冲突.
根据内核的配置,THREAD_SIZE既可以是4K字节(1个页面)也可以是8K字节(2个页面),thread_info是52个字节长.
下图是当设为8KB时候的内核堆栈:Thread_info在这个内存区的开始处,内核堆栈从末端向下增长.进程描述符不是在这个内存区中,而分别通过task与thread_info指针使thread_info与进程描述符互联.所以获得当前进程描述符的current定义如下:
宏定义current通过下面的函数获取
static inline struct task_struct * get_current(void)
{
return current_thread_info()->task;
}
static inline struct thread_info * current_thread_info(void)
{
struct current_thread_info *ti;
__asm__(“andl %%esp, %0”;”:”=r” (ti) : “0” (~(THREAD_SIZE-1)));
return ti;
}
根据THREAD_SIZE大小,分别屏蔽掉内核栈的12-bit LSB(4K)或13-bit LSB(8K),从而获得内核栈的起始位置.将当前指针%esp与数值~(THREAD_SIZE-1) 按位与运算,并将结果给ti.ti 保存的值恰好是内核态栈中的最低位地址,这里正是进程描述中成员变量thread_info所在的位置,也即当前进程描述符的成员变量thread_info的地址.