假设一个进程的页表如下所示_页表描述符(page table descriptor)

3805632013c68a46e3ea83a9119b5d7b.png

之前的文章曾提到,在多级页表中,每一级页表的entry除了存放下一级页表(对于PTE来说是页)的首地址,还留下了不少bit空间可供使用,本文将就此详细介绍下。

以32位的x86为例,从存储page首地址的PTE(page table entry)说起。

d8e693ab02ef46322e98606e11b0cbb8.png

通常page frame的大小为4KB,所以PTE中指向的page首地址是4KB对齐的,也就是说page首地址的最后12个bit都为0。而一个PTE的大小为4字节,所以这后面的12个bit可以被用来表达page的属性。

8c20af5307dc1b1bb2ef99f026c0a1b5.png

P(Present) - 为1表明该page存在于当前物理内存中,为0则PTE的其他部分都失去意义了,不用看了,直接触发page fault。P位为0的PTE也不会有对应的TLB entry,因为早在P位由1变为0的时候,对应的TLB就已经被flush掉了。

G (Global)- 这个标志位在这篇文章中有介绍,主要是用于context switch的时候不用flush掉kernel对应的TLB,所以这个标志位在TLB entry中也是存在的。

A(Access) - 当这个page被访问(读/写)过后,硬件将该位置1,TLB只会缓存access的值为1的page对应的映射关系。软件可将该位置0,然后对应的TLB将会被flush掉。这样,软件可以统计出每个page被访问的次数,作为内存不足时,判断该page是否应该被回收的参考。

D (Dirty)- 这个标志位只对file backed的page有意义,对anonymous的page是没有意义的。当page被写入后,硬件将该位置1,表明该page的内容比外部disk/flash对应部分要新,当系统内存不足,要将该page回收的时候,需首先将其内容flush到外部存储。之后软件将该标志位清0。

R/W和U/S属于权限控制类:

R/W(Read/Write) - 置为1表示该page是writable的,置为0则是readonly,对只读的page进行写操作会触发page fault。

U/S(User/Supervisor) - 置为0表示只有supervisor(比如操作系统中的kernel)才可访问该page,置为1表示user也可以访问。

PCD和PWT和cache属性相关:

PCD(Page Cache Disabled)- 置为1表示disable,即该page中的内容是不可以被cache的。如果置为0(enable),还要看CR0寄存器中的CD位这个总控开关是否也是0。

PWT (Page Write Through)- 置为1表示该page对应的cache部分采用write through的方式,否则采用write back。

再来看看存储page table首地址的PDE(page directory entry)里的标志位。

3a1cea286ea9939d661e180b20c39a70.png

上图第三行P位为0,处理方法和PTE是一样的。第二行是采用4KB大小page的情况,PDE指向下一级page table,则G位和Dirty位是无效的。PS(page size)位在PDE里就有意义了,为0表示为normal size的page(即4KB)。为1表示large page,即

,PDE直接指向large page,如第一行所示(其实,还要看CR4寄存中PSE - page size enable这个总控开关位是否为1)。

63720535edbea1ccc8c3d015a3f4cff9.png

再往上走就是存储当前进程的page directory首地址的CR3寄存器了,CR3里只有PCD和PWT位是有效的。

b60b5ea4495476216160de719f93a70a.png

之前的文章中称页表中的一个item为entry,是因为它存储了下级页表(或者页)的入口地址。但它同时存储了下级页表的诸多属性,可视为对下级页表的描述,因此又常被称为page table descriptor。类似的概念也有用在其他地方,比如从80286处理器开始引入的segment descriptor,高速网卡收发中用到的buffer descriptor等等。

【64位系统

现在64位系统已经被广泛使用了,我们来看看page table descriptor在64位系统中有什么变化。

目前64位系统多采用48位虚拟地址,除了页表可扩展为4级(甚至5级),每级页表的高16位(5级的话是高7位)也被空了出来。其实低12位对属性的描述已经比较全面了,而且高16位通常需要预留给以后的扩展(比如5级页表),所以64位descriptor也就是在最高位(bit63)加入了一个XD(eXecute Disable)。

它跟CD一样,为1表示disable,与一般的逻辑是反的。在32位的x86中,没有对page是否可执行的权限控制位,也就是说你可以将stack上的数据当做代码来执行,这会被hacker利用来制造攻击。

另外,64位CR3中加入了对PCID的支持,因为CR3存放的是当前进程的页表首地址,所以CR3是最适合放PCID的了,但是高16位通常保留,所以只能塞到低12位和PCD,PWT共用bit位了。

当CR4寄存器的PCIDE位为1,此时这低12位就表示PCID,PCD和PWT就被覆盖了,那就默认为0吧,反正大部分情况下都是cache enable, write back的嘛。PCID位为0,PCD和PWT才又重见天日,可以在0和1之间自由切换了。

1d4dd1f19f8ed2e6117d026b093c80d8.png

参考:

《Intel Softwate Development Manual Volume 3》Chapter 4 - Paging

原创文章,转载请注明出处。

### 进程描述符的概念与实现 #### 什么是进程描述符? 在现代操作系统中,**进程描述符**是一个用于表示和管理进程的数据结构。具体来说,它是用来存储与某个特定进程相关的所有信息的核心数据结构[^1]。对于Linux系统而言,这个数据结构被称为`task_struct`。 #### `task_struct` 的组成 `task_struct` 是 Linux 内核中最重要的数据结构之一,它包含了与一个进程相关的几乎所有必要信息。这些信息可以分为以下几个主要类别: 1. **进程状态**: 表示当前进程的状态,例如运行态 (`TASK_RUNNING`)、睡眠态 (`TASK_INTERRUPTIBLE`, `TASK_UNINTERRUPTIBLE`) 或僵尸态 (`EXIT_ZOMBIE`) 等[^5]。 2. **进程标识符 (PID)**: 唯一识别该进程的编号。 3. **内存管理信息**: 描述进程使用的虚拟地址空间,包括页表和其他内存映射细节。 4. **文件系统信息**: 包括文件描述符表以及其他 I/O 资源的相关信息[^3]。 5. **调度信息**: 提供 CPU 时间片分配所需的信息,如优先级、时间片计数器等。 6. **信号处理机制**: 存储待处理的信号队列及相关配置。 7. **其他辅助信息**: 如父进程 ID、子进程链表、线程组信息等。 由于 `task_struct` 需要容纳如此多的内容,因此它的复杂度较高,并且还包含许多指向外部数据结构的指针[^2]。 #### 文件描述符进程的关系 每个进程都有自己的独立文件描述符表,这使得不同进程之间的 I/O 资源能够相互隔离。当需要跨进程共享某些文件句柄时,则可以通过传递底层的 `struct file*` 来完成这一目标[^4]。 以下是简化版的 `task_struct` 定义示例: ```c struct task_struct { volatile long state; /* Process state */ pid_t pid; /* Process identifier */ struct list_head tasks; /* List of all processes */ struct mm_struct *mm; /* Memory management information */ struct files_struct *files; /* File descriptor table pointer */ }; ``` 此代码片段仅展示了部分字段以帮助理解其基本构成。 #### 总结 通过上述分析可以看出,在基于 Linux 的操作系统里,`task_struct` 承担着至关重要的角色——作为进程描述符来全面记录每一个活跃进程中涉及的各种参数与特性。这种设计不仅增强了系统的灵活性也提高了效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值