2024年【操作系统】进程数据结构_进程地址空间内核数据结构是在哪里,来自阿里巴巴佛系C C++程序员的指南

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取


![在这里插入图片描述](https://img-blog.csdnimg.cn/9ec86b7372bc42b296299b8143d2e1d2.png)



> 
> 如果在 bash 上使用 GDB 来 debug 一个进程,这个时候 GDB 是 real\_parent,bash 是这个进程的 parent。
> 
> 
> 


* parent 指向其父进程。当它终止时,必须向它的父进程发送信号。
* children 表示链表的头部。链表中的所有元素都是它的子进程。
* sibling 用于把当前进程插入到兄弟链表中。


### 进程权限


在 Linux 里面,对于进程权限的定义如下



/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
//Objective (客): 谁能操纵我 real_cred
//Subjective(主) : 我能操纵谁 cred
//进程的凭证集可用结构cred表示
//cred 的定义
struct cred {

kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */

kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we’re permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */

} __randomize_layout;


* uid 和 gid :real user/group id 谁启动的进程,就是谁的 ID。
* euid 和 egid: 当这个进程要操作消息队列、共享内存、信号量等对象的时候,其实就是在 比较这个用户和组是否有权限
* fsuid 和 fsgid: filesystem user/group id。这个是对文件操作会审核的 权限。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/efc42104c3ef4f7ca9004bf8d073b101.png)



> 
> 游戏进程的 uid、 euid、fsuid 都是用户 A。第二个图,只有r x,只能运行保存不了,可以通过`chmod u+s program` 给这个游戏程序设置 `set-user-ID` 的标识位,权限变成 rwsr-xr-x,然后用户 A 再启动这个游戏 的时候,创建的进程 uid 当然还是用户 A,但是 euid 和 fsuid 就不是用户 A 了,因为看 到了 `set-user-id` 标识,就改为文件的所有者的 ID,也就是说,euid 和 fsuid 都改成用户 B 了,这样就能够将通关结果保存下来。  
>  在 Linux 里面,一个进程可以随时通过 setuid 设置用户 ID,所以,游戏程序的用户 B 的 ID 还会保存在一个地方,这就是 \*\*suid 和 sgid,也就是 saved uid 和 save gid。\*\*这样就可 以很方便地使用 setuid,通过设置 uid 或者 suid 来改变权限。
> 
> 
> 


除了以用户和用户组控制权限,Linux 还有另一个机制就是**capabilities**,capabilities,用位图表示权限,在 capability.h 可以找到定义的 权限。



#define CAP_CHOWN 0
#define CAP_KILL 5
#define CAP_NET_BIND_SERVICE 10
#define CAP_NET_RAW 13
#define CAP_SYS_MODULE 16
#define CAP_SYS_RAWIO 17
#define CAP_SYS_BOOT 22
#define CAP_SYS_TIME 25
#define CAP_AUDIT_READ 37
#define CAP_LAST_CAP CAP_AUDIT_READ


* cap\_permitted 表示进程能够使用的权限。但是真正起作用的是 cap\_effective
* cap\_bset,也就是 capability bounding set,是系统中所有进程允许保留的权限.
* cap\_ambient 是比较新加入内核的,就是为了解决 cap\_inheritable 鸡肋的状况


### 内存管理


每个进程都有自己独立的虚拟内存空间,这需要有一个数据结构来表示,就是 mm\_struct



struct mm_struct *mm;
struct mm_struct *active_mm;


### 文件与文件系统


每个进程有一个文件系统的数据结构,还有一个打开文件的数据结构。这个我们放到文件系统那一节详细讲述。



/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;


### 小结 看图复述


![在这里插入图片描述](https://img-blog.csdnimg.cn/d70b75fe79184c529589129b7037d9d0.png)



> 
> 用户态的执行和内核态的执行的连接需要变量 `struct thread_info thread_info; 和 void *stack;`
> 
> 
> 


## 用户态函数栈


* 程序的执行往往是一个函数调用另一个函数。函数调用都是通过栈来进行的.
* 函数调用其实也很简单 , 就是指令的跳转,比如函数的调用过程,A 调用 B、调用 C、调用 D,然后返回 C、返回 B、返回 A,这是 一个后进先出的过程。
* 在进程的内存空间里面,栈是一个从高地址到低地址,往下增长的结构,也就是上面是栈 底,下面是栈顶,入栈和出栈的操作都是从下面的栈顶开始的。


![在这里插入图片描述](https://img-blog.csdnimg.cn/7400bdb193ce4b339e188158e12681c6.png)


* 上图是 `32位系统的操作系统`,`CPU` 里,`ESP(Extended Stack Pointer)`是栈顶 指针寄存器,入栈操作 `Push` 和出栈操作 `Pop` 指令,会自动调整 `ESP` 的值。另外有一个寄 存器`EBP(Extended Base Pointer)`,是栈基地址指针寄存器,指向当前栈帧的最底部。
* [栈帧知识点](https://bbs.youkuaiyun.com/topics/618668825)
* A 调用 B,A 的栈里面包含 A 函数的局部变量,然后是调用 B 的时候要传给它的参 数,然后返回 A 的地址,这个地址也应该入栈,这就形成了 A 的栈帧。接下来就是 B 的栈 帧部分了,先保存的是 A 栈帧的栈底位置,也就是 EBP。因为在 B 函数里面获取 A 传进来 的参数,就是通过这个指针获取的,接下来保存的是 B 的局部变量等等。  
 当 B 返回的时候,返回值会保存在 EAX 寄存器中,从栈中弹出返回地址,将指令跳转回 去,参数也从栈中弹出,然后继续执行 A  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d0b39d85ba434ad4977a83efd1e10be7.png)
* 上图为64位操作系统 : rax 用于保存函数调用的返回结果。栈顶指针寄存器变成了 rsp,指向栈顶位置。堆栈的 Pop 和 Push 操作会自动调整 rsp,栈基指针寄存器变成了 rbp,指向当前栈帧的起始位置, 改变比较多的是参数传递。rdi、rsi、rdx、rcx、r8、r9 这 6 个寄存器,用于传递存储函数 调用时的 6 个参数。如果超过 6 的时候,还是需要放到栈里面。前 6 个参数有时候需要进行寻址,但是如果在寄存器里面,是没有地址的,因而还 是会放到栈里面,只不过放到栈里面的操作是被调用函数做的。  
 **以上的栈操作,都是在进程的内存空间里面进行的**


## 内核态函数栈


内核态的函数调用 : 成员变量 `void *stack;`,也就是内核栈


* Linux 给每个 task 都分配了内核栈。



//arch/x86/include/asm/page_32_types.h 32位
#define THREAD_SIZE_ORDER 1
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER) //PAGE_SIZE 是 4K,左移一位就是乘以 2,也就是 8K。

//arch/x86/include/asm/page_64_types.h 64位
#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif

#define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER) //PAGE_SIZE 的基础上左移两位
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER) //16K


**内核栈是一个非常特殊的结构,如下图所示:**


![在这里插入图片描述](https://img-blog.csdnimg.cn/14e5a1f5034943a0a24a834aadaa98ca.png)


* 这段空间的最低位置,是一个 thread\_info 结构。这个结构是对 task\_struct 结构的补充, 与体系结构有关的.



//内核代码里面有这样一个 union,将 thread_info 和 stack 放在一起,在 include/linux/sched.h 件中就有
union thread_union {
#ifndef CONFIG_THREAD_INFO_IN_TASK
struct thread_info thread_info;
#endif
unsigned long stack[THREAD_SIZE/sizeof(long)];
};

//在内核栈的最高地址端,存放的是另一个结构 pt_regs,定义如下。其中,32 位和 64 位的定义不一样。看图


* 当系统调用从 用户态到内核态的时候就是将用户态运行过程中的CPU上下文保存起来,其实主要就是保存在这个结构的寄存器变量里,返回的时候才能在过程运行的地方进行运行下去。
* 在内核中,CPU 的寄存器 ESP 或者 RSP,已经指向内核栈的栈顶,在内核态里的调用都有 和用户态相似的过程


## 通过 task\_struct 找内核栈



//task_struct 的 stack 指针 函数找到这个线程内核栈
static inline void *task_stack_page(const struct task_struct *task) {
return task->stack;
}
//task_struct 获取pt_regs
#define task_pt_regs(task)
({
unsigned long __ptr = (unsigned long)task_stack_page(task);
__ptr += THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;
((struct pt_regs *)__ptr) - 1;
})
//这是先从 task_struct 找到内核栈的开始位置。然后这个位置加上 THREAD_SIZE 就到了最后的位置,然后转换为 struct pt_regs,再减一,就相当于减少了 一个 pt_regs 的位置,就到了这个结构的首地址


压栈 pt\_regs 有两种情况: ①涉及权限的 改变,会压栈保存 SS、ESP 寄存器的,这两个寄存器共占用 8 个 byte。 ②不涉及权限的变化,就不会压栈这 8 个 byte  
 **现在如果你 task\_struct 在手,就能够轻松得到内核栈和内核寄存器**


## 通过内核栈找 task\_struct



//thread_info 结构
struct thread_info {
struct task_struct *task; /*task 指向 task_struct*/
//常用 current_thread_info()->task 来获取 task_struct
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
mm_segment_t addr_limit;
unsigned int sig_on_uaccess_error:1;
unsigned int uaccess_err:1; /* uaccess failed */
};

static inline struct thread_info *current_thread_info(void)
{
return (struct thread_info *)(current_top_of_stack() - THREAD_SIZE);
//内存栈最高地址 - THREAD_SIZE,就到了 thread_info 的起始地址
}


* 新的机制里面,每个 CPU 运行的 task\_struct 不通过 thread\_info 获 取了,而是直接放在 Per CPU 变量里面了
* 多核情况下,CPU 是同时运行的,但是它们共同使用其他的硬件资源的时候,我们需要解 决多个 CPU 之间的同步问题
* Per CPU 变量是内核中一种重要的同步机制,Per CPU 变量就是为每个 CPU 构 造一个变量的副本,这样多个 CPU 各自操作自己的副本,互不干涉。
* 系统刚刚初始化的时候,current\_task 都指向 init\_task


## 总结



> 
> 在用户态,应用程序进行了至少一次函数调用。32 位和 64 的传递参数的方式稍有不 同,32 位的就是用函数栈,64 位的前 6 个参数用寄存器,其他的用函数栈。 在内核态,32 位和 64 位都使用内核栈,格式也稍有不同,主要集中在 pt\_regs 结构 上。  
>  在内核态,32 位和 64 位的内核栈和 task\_struct 的关联关系不同。32 位主要靠 thread\_info,64 位主要靠 Per-CPU 变量。
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/33cbf2b45aaa4e0da0183212c88ac1e4.png)








![img](https://img-blog.csdnimg.cn/img_convert/f51330ca77066574e6c5f8d25bc66f11.png)
![img](https://img-blog.csdnimg.cn/img_convert/3f93d6783e6af7aff1d7237f3c228812.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**






[外链图片转存中...(img-XjslLuzs-1715591659236)]
[外链图片转存中...(img-ed8XgWS2-1715591659236)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值