admin管理员组

文章数量:1606659

提出问题

内核常用的 current 指针是怎么实现的?

内核栈

要理解 current 指针如何实现的,就必须了解 内核栈。

什么是进程的内核栈?
在内核态(比如应用进程执行系统调用)时,进程运行需要自己的堆栈信息(不是原用户空间中的栈),而是使用内核空间中的栈,这个栈就是进程的内核栈

进程的内核栈在计算机中是如何描述的?
linux中进程使用task_struct数据结构描述,其中有一个stack指针:

struct task_struct
{
    // ...
    void *stack;    //  指向内核栈的指针
    // ...
};

task_struct数据结构中的stack成员指向 thread_union 结构(Linux内核通过thread_union联合体来表示进程的内核栈)

// 内核线程栈 可以通过内核配置成4K 或者 8K,此处是8K。
// 在X86体系结构上,32位的内核栈为8K,64位的为16K。
#define THREAD_SIZE        8192       

union thread_union {
	struct thread_info thread_info;
	unsigned long stack[THREAD_SIZE/sizeof(long)];
};

thread_info 是记录部分进程信息的结构体,其中包括了进程上下文信息:

struct thread_info {
	struct pcb_struct	pcb;		/* palcode state */
 
 	// 这里很重要!! task指针 指向的是所创建的进程的struct task_struct
	struct task_struct	*task;		/* main task structure */  
	unsigned int		flags;		/* low level flags */
	unsigned int		ieee_state;	/* see fpu.h */
 
	struct exec_domain	*exec_domain;	/* execution domain */
	mm_segment_t		addr_limit;	/* thread address space */
	unsigned		cpu;		/* current CPU */
	int			preempt_count; /* 0 => preemptable, <0 => BUG */
 
	int bpt_nsaved;
	unsigned long bpt_addr[2];		/* breakpoint handling  */
	unsigned int bpt_insn[2];
 
	struct restart_block	restart_block;
};

也就是说:

  • thread_info 中有个指针指向了 task_struct
  • task_struct 中有个指针指向了 thread_info
    如下图所示:

    从用户态刚切换到内核态以后,进程的内核栈总是空的。因此,esp寄存器指向这个栈的顶端,一旦数据写入堆栈,esp的值就递减。

整个8K的空间,顶部供进程堆栈使用,最下部为thread_info。从用户态切换到内核态时,进程的内核栈还是空的,所以sp寄存器指向栈顶,一旦有数据写入,sp的值就会递减,内核栈按需扩展,理论上最大可扩展到 【8192- sizeof(thread_info) 】大小,考虑到函数的现场保护,往往不会有这么大的栈空间。内核在代表进程执行时和所有的中断服务程序执行时,共享8K的内核栈。

current 原理

现在就可以说明 current 原理了。

在内核中,可以通过current宏来获得当前执行进程的task_struct指针。现在来简要分析以下:
最原始的定义如下:

    #define get_current() (current_thread_info()->task)
    #define current get_current()
 

可以看出,current调用了 current_thread_info 函数(此函数的内核路径为: arch/arm/include/asm/thread_info.h,内核版本为2.6.32.65)

static inline struct thread_info *current_thread_info(void)
   {
        register unsigned long sp asm ("sp");
        return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
   }

当内核线程执行到此处时,其SP堆栈指针指向调用进程所对应的内核线程的栈顶。通过 sp & ~(THREAD_SIZE-1)向上对齐,达到栈底部。如下图所示:

将结果强制类型转换为thread_info类型,此类型中有一个成员为task_struct,它就是 当前正在运行进程的 task_struct指针。

至此,你应该明白了 current 是怎么来的了吧?

为什么要有 thread_info

thread_info 保存了进程描述符中频繁访问和需要快速访问的字段,内核依赖于该数据结构来获得当前进程的描述符(为了获取当前CPU上运行进程的task_struct结构,内核提供了current宏。

内核还需要存储每个进程的PCB信息, linux内核是支持不同体系的的, 但是不同的体系结构可能进程需要存储的信息不尽相同,这就需要我们实现一种通用的方式, 我们将体系结构相关的部分和无关的部门进行分离,用一种通用的方式来描述进程, 这就是struct task_struct, 而thread_info
就保存了特定体系结构的汇编代码段需要访问的那部分进程的数据,我们在thread_info中嵌入指向task_struct的指针, 则我们可以很方便的通过thread_info来查找task_struct.

参考:

https://wwwblogs/cherishui/p/4255690.html
https://blog.csdn/tiankong_/article/details/75647488

本文标签: 指针内核操作系统Current