内核进程实现
本文章基于 Linux 2.6.11 编写
内核栈
每个进程的执行流都需要一个栈空间来保存代码执行时所有上下文,包括变量地址和上层函数地址等,在内核态,这个栈就是内核栈。Linux的进程切换还需要使用汇编手动的将一些寄存器值存放在内核栈中,内核栈由以下结构体定义给出。
#define THREAD_SIZE (8192)
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
struct thread_info {
struct task_struct *task; /* 进程描述符地址*/
unsigned long flags; /**< 存放 TIF_XXX
* 最重要的就是 TIF_NEED_RESCHED
*/
unsigned long status; /* thread-synchronous flags */
__u32 cpu; /**< 所在的运行CPU current CPU */
__s32 preempt_count; /**< 调度计数器,等于0,方可调度 */
...
};
大多情况下,我们都将内核栈配置成 8K 大小,所以在进行内核开发时,函数中不要使用过大的栈变量。为了快速的传递和访问当前进程内核态的一些重要数据,我们将这些数据称为线程信息 struct thread_info
,并存放在内核栈的起始处,如下图。
__va(x) 用于计算物理页号对应的虚地址
__va(pfn+2) +-----------+<--- ebp 栈基地址
| stack |
| || |
| || | 栈向低地址增长
| || |
8096 | \/ |
+-----------+<--- esp 当前栈顶
| unused |
+-----------+
|thread_info|
__va(pfn) +-----------+<--- current 指向处
当使用 esp
的值进行简单的位运算,屏蔽掉低 13 位的有效位就可以得到每个进程的 thread_info
结构的地址,其中存放了 包括 struct task
进程描述地址 在内的重要信息,这样做我们就不必在每个需要使用这些信息的内核函数参数中携带 thread_info
地址指针。由于 struct task
也包含 thread_info
指针,所以整个算法是直接返回当前进程的 task
进程描述符,并使用 current
表示。
static inline struct thread_info *current_thread_info(void)
{
struct thread_info *ti;
__asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
return ti;
}
static inline struct task_struct * get_current(void)
{
return current_thread_info()->task;
}
#define current get_current()
0号始祖进程
当一个CPU启动后,第一个执行流在经过一系列初始化后就形成0号进程,0号进程不做任何实际的工作,只是运行一个空函数,该函数除了检查是否已被抢占,就仅仅死循环执行空指令或挂起CPU等待中断,直到系统停止。可以配置多种不同的空函数,下面给出默认空函数。
void cpu_idle(void)
{
...
while (1