内核问答

以下问题大部分出自导师,小部分是自己思考后提出。答案大部分自己总结,由于水平有限,不排除有错漏的可能,如果发现有问题,欢迎指出。

内存部分(有部分进程、系统调用)

逻辑地址、虚拟地址、线性地址、物理地址的区别是什么?

答:
逻辑地址是编程时使用的地址,比如常用到的指针,指针内存的是某变量的地址,这个地址指的就是逻辑地址。

物理地址是用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。

虚拟地址线性地址是一个东西,都是指从逻辑地址转换到虚拟地址的中间的一个地址,只有在保护模式中才使用。因为它不是真实存在的地址,与物理地址相对应,所以叫虚拟地址,也因为它的地址空间是线性的,所以也叫线性地址。

(有时候好像也会用虚拟地址代指逻辑地址,还是尽量避免这种情况吧。)

实模式

在实模式中,逻辑地址=段基址+段内偏移,即段寄存器中存放段基址的高16位,段内偏移也只有16位。

物理地址 = 段寄存器内的值 << 4 + 偏移量

实模式下没有线性地址。

保护模式

保护模式下的转换相对比较复杂,简单总结如下:

逻辑地址通过分段机制转换成线性地址。

线性地址再通过分页机制转换成物理地址。

注意:Linux对于分段机制只是简单使用,四个主要的代码段与数据段的基址都是0,限长都是4GB,所以线性地址的值其实和逻辑地址的偏移量的值是一致的。

为什么每个进程都需要有自己的页表,每个线程是否有自己的页表,每个内核线程呢?

为什么每个进程都需要有自己的页表?

每个进程都需要有自己的页表,因为每个进程的地址空间都应该是独立的,即不同进程间,大多数数据应该是独立的,不能互相访问的。

每个进程都拥有自己的页表,可以保证别的进程在一般情况下,是访问不到它的数据的,因为两个不同进程即便使用相同的线性地址,访问的也是各自的地址空间,指向的物理地址也一般不会相同。

可以思考这样一个问题,假如多个进程共同享有一个页表,如何保证彼此互不影响呢?

在每个页表项上标注该页的拥有者(某一个进程)也许是一个解决办法,就像linux里的文件一样,但是这样一来,开销就太大了,在空间上,需要存储进程的id,在时间上,每次访问页时都需要对页的拥有者进行验证……

由此可以看出,独立的页表,或者说独立的地址空间,对于作为资源分配的单位的进程来说,是相当必要的。

每个线程是否有自己的页表?

要回答这个问题,先要明白线程的含义与作用,以及线程与进程的区别。

进程是分配系统资源(CPU时间、内存等)的实体。

而一个进程,可能由多个线程组成。每个线程负责进程的一小部分工作。

举个例子,在电脑上打开qq,这就是一个进程。如果在qq中同时打开多个聊天窗口,那么每个聊天窗口就是一个线程。

每个线程拥有一些独立的资源,但大部分资源,都是和其他所有线程,也就是整个进程共享的。

而对于内存资源来说,最好最简单的共享方法,无疑就是共享同一个地址空间,也就是说,共享页表。

对于线程来说,它的独立性没有进程那么强,反而有大量需要和其他线程共享的内存资源,所以,线程不需要有自己的页表。

内核线程是否有自己的页表?

上面提到的线程,其实是用户线程

还有一种线程,叫内核线程,负责刷新磁盘高速缓存,交换不用的页框,维护网络连接等任务。

内核线程只运行在内核态,而普通进程既可以运行在内核态,也可以运行在用户态。

也正因为内核线程只运行在内核态,所以用户空间对它而言没有意义,它只使用内核空间,也就是大于PAGE_OFFSET的线性地址空间。

而内核空间的相应的页表项都应该是相同的,一个内核线程使用什么样的页表集根本就没有什么关系。

实际上,为了避免不必要的TLB和高速缓存刷新,内核线程使用的是最近运行的进程的页表。

进程描述符是task_struct类型结构,每个进程描述符中有两个内存描述符指针:mm和active_mm。在每个内存描述符中,有一个字段是指向页全局目录的指针。pgd_t * pgd;

mm指向进程所拥有的内存描述符,而active_mm指向进程运行时所使用的内存描述符。

对于普通进程而言,这两个字段存放相同的指针,但内核线程不拥有任何内存描述符,它们的mm字段中为NULL,当内核线程得以运行时,它的active_mm字段被初始化为前一个运行进程的active_mm值。

内核会确保对主内核页全局目录的修改(一般是通过vmalloc和vfree)能传递到由进程使用的页全局目录中。

进程切换(换入、换出)时是如何保留自己的页表的?

从本质上来讲,每个进程切换由两步组成。

  1. 切换页全局目录以安装一个新的地址空间;

  2. 切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包括CPU寄存器。

事实上,页表无需特意保留,因此在进程描述符里的内存描述符mm中,有一个pgd字段,保留了页全局目录的地址,换出的进程在之后被换入时,只需要将对应进程的pgd字段加载到cr3寄存器中即可安装新的地址空间。

cr3中是页目录表的物理地址,而指针是逻辑地址,是怎么实现相互转换的呢?

页目录表放在内核空间的低端部分,物理地址和逻辑地址的转换很简单,加减PAGE_OFFSET即可。

X86_32的虚拟地址空间是如何分布的?X86_64的虚拟地址空间是如何分布的?

X86_32的虚拟地址空间分布

0-3G是用户空间
3G-4G是内核空间。
内核空间的低896MB直接映射,高128MB用于永久内核映射,临时内核映射,固定映射,非连续内存区。

X86_64的虚拟地址空间分布

以下信息来自 \linux-3.10.0-693.el7\Documentation\x86\x86_64\mm.txt

<previous description obsolete, deleted>
Virtual memory map with 4 level page tables:
0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm hole caused by [48:63] sign extension 
ffff800000000000 - ffff80ffffffffff (=40 bits) guard hole 
ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory 
ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole 
ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space 
ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole 
ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB) ... unused hole ... ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0 
ffffffffa0000000 - ffffffffff5fffff (=1526 MB) module mapping space 
ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls 
ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole
The direct mapping covers all memory in the system up to the highest memory address (this means in some cases it can also include PCI memory holes).
vmalloc space is lazily synchronized into the different PML4 pages of the processes using the page fault handler, with init_level4_pgt as reference.
Current X86-64 implementations support up to 46 bits of address space (64 TB), which is our current limit. This expands into MBZ space in the page tables.
->trampoline_pgd:
We map EFI runtime services in the aforementioned PGD in the virtual range of 64Gb (arbitrarily set, can be raised if needed)
0xffffffef00000000 - 0xffffffff00000000
Note that if CONFIG_RANDOMIZE_MEMORY is enabled, the direct mapping of all physical memory, vmalloc/ioremap space and virtual memory map are randomized. Their order is preserved but their base will be offset early at boot time.
-Andi Kleen, Jul 2004 

不同页表的两个页表项可以指向同一个页吗?

可以。

内核通过页描述符记录每个物理页的信息,描述符中有一个字段是atomic_t _count,页的引用计数器。

该字段为-1,说明相应物理页空闲,可以被分配给任一进程或内核本身;如果该字段大于或等于0,则说明页框被分配给了一个或多个进程,或者用于存放一些内核数据结构。

由上面的描述就可以看出,同一个物理页,是有可能被分配给多个进程的。

比如共享内存。还有采用Copy On Write技术的父子进程的页表。

x86:32位、PAE、64位,分别的页表组织是怎样?

32位

RAM为4GB,线性地址32位,物理地址也是32位,cr3寄存器中存放页目录表的物理地址。

页目录项中PS=0,页大小为4KB时。

根据cr3控制寄存器找到页目录表,每个页目录表项4字节,最多1024项。

线性地址最高10位是Directory字段,即页目录表的索引,根据该字段可以找到一个具体的页目录表项(2^10 = 1024)。

页目录表项中存放着页表的基址,可以找到一个具体页表。

线性地址中间10位是Table字段,是页表的索引,根据该字段可以找到一个具体的页表项。

页表项结构与页目录表项相同,里面存放一个物理页的基址。

线性地址最低12位是Offset字段,是页内偏移,由此可以找到具体某一个字节的物理地址。

线性地址分级为10+10+12

PS = 1,页大小为4MB

cr3指向页目录表。

线性地址最高10位指向页目录表1024个项中的一个,每个项都指向一个物理页。

线性地址低22位作为页内偏移,找到一个字节的物理地址。

线性地址分级为10+22

PAE

RAM为64GB,线性地址32位,但物理地址36位,cr3寄存器存放页目录指针表(PDPT)的基地址。

一个页目录指针表中,有4个64位表项。

PS=0,即页大小为4KB时。

cr3指向一个PDPT

线性地址最高两位(31-30)指向PDPT4个项中的一个。每个项都指向一个页目录表。

64GB的RAM被分成2^24个物理页,页表项的物理地址字段从20位扩展到了24位,再加上12个标志位,32位的页表项已经满足不了需求。为了对齐方便访问,所以每个页表项大小从32位变成了64位。

页目录表中页目录表项的数目也从1024变成了512。

线性地址的位(29-21),9位,指向页目录表中的512个项中的一个,而每一个项都指向一个页表。

线性地址位20-12,9位,指向页表中512个项中的一个,而每一个项都指向一个物理页。

线性地址位11-0,12位,作为页内偏移,最终找到一个字节的物理地址。

线性地址分级为2+9+9+12

PS=1,页大小为2MB

cr3指向一个PDPT

位31-30指向PDPT中4个项中的一个

位29-21指向页目录中512个项中的一个

位20-0,2MB页中的偏移量

线性地址分级为2+9+21

64位

4级分页。

采用页全局目录,页上级目录,页中间目录,页表,具体实现与32位类似。

页大小为4KB,寻址使用48位,线性地址分级为 9+9+9+9+12。

高16位被用作符号扩展,这高16位要么全是0,要么全是1。

x86架构下的内存为什么分为ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGH? X86_64下包含哪些Zone类型?

x86架构下的内存为什么分为ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGH?

因为80x86架构有两种硬件约束:

  1. ISA总线的直接内存存取(DMA)处理器有一个严格的限制:它们只能对RAM的前16MB寻址;

  2. 在具有大容量RAM的现代32位计算机中,CPU不能直接访问所有物理内存。以Linux为例,在初始化阶段,内核只把896MB的RAM窗口映射到内核线性地址空间,如果一个程序需要对现有RAM的其他部分寻址,那就必须把某些其他线性地址间隔映射到所需的RAM。。

因为第一种限制,低于16MB的内存被称为ZONE_DMA,单独作为一个管理区。该区包含的页框可以由老式基于ISA的设备通过DMA使用。

在x86_64架构下,还需要ZONE_DMA32,因为需要32位地址寻址的DMA.这部分是高于16MB,低于4GB。

因为第二种限制,高于896MB的内存页框被称为ZONE_HIGHMEM,不能由内核直接访问。64位体系结构上ZONE_HIGHMEM总是为空。

剩余的部分,即高于16MB,低于896MB的部分,被称为ZONE_NORMAL,内核可以直接进行访问,但不能通过DMA使用。

X86_64下包含哪些Zone类型?

和x86相比,多了ZONE_DMA32,少了ZONE_HIGHMEM(因为线性地址空间足够大,可以把所有物理内存直接映射到内核。)

0-16MB,是ZONE_DMA

16MB-4GB,是ZONE_DMA32

4GB以上,是ZONE_NORMAL

伙伴系统算法的原理?

将所有空闲页框分组成11个块链表,每个块链表的节点分别是1,2,4,8,16,32,64,128,256,512,1024个连续的页框。
当需要分配内存时,去寻找最小的,可以满足条件的块。

比如要请求分配一个250个页框的内存。

1.去256个页框的链表中寻找,如果有空闲的块,就将它分配给请求内存的进程。

  1. 如果没有空闲的块,就去寻找下一个更大的页块,即512个页框的块。如果有空闲块,就将该块分成两等份,一半分配给进程,另一半插入到256个页框的链表中。

3.如果512个页框的链表中也没有空闲块,也去寻找下一个更大的页块,即1024个页框的块。如果有,就先将该页框两等分,其中一半插入512个页框的链表中,另一半再次二等分,一半插入256个页框的链表中,另一半分配给进程。

4.如果1024个页框的链表中也没有空闲块,就放弃分配内存并发出出错信号。

而释放过程就是以上过程的逆过程,在释放后悔试图把一对空闲伙伴块合并。

伙伴算法可以尽可能避免外部碎片的产生,因为剩下的所有空闲块,最小也是1个页框,完全可以分配给需要的内存。

而合并操作也保证了连续空间尽可能大。

但它会导致内部碎片。

释放一个page到伙伴系统后,这个page需要跟旁边的空闲page合并成更大的块,那么是跟左边的合并 还是跟右边的合并呢?

答案取决于块的地址。

在伙伴系统中,对于伙伴的定义如下:

  1. 两个块具有相同的大小,记作b
  2. 它们的物理地址是连续的
  3. 第一个块的第一个页框的物理地址是 2 x b x 2^12的倍数。

第三个条件是为了保证内存对齐,也是必须满足的条件。

所以,如果释放的page,也可以扩大范围到包含b个页框的块(对于单独的一页,b就为1),它的第一个页框物理地址是2 x b x 2^12的倍数,那它就和右边(地址比它大)的块合并,否则,就和左边的块合并。

pthread_create()和fork()有什么区别?

pthread_creat()和fork()都是用户态的API,但作用不同。一个是创建线程,一个是创建进程。

它们调用的系统调用也不同,一个调用了clone(),一个调用了fork()。

这两个系统调用的服务例程自然也不相同,一个是sys_clone(),一个是sys_fork(),但两个服务例程都调用了do_fork()函数。

asmlinkage long sys_fork(struct pt_regs *regs)
{
   
	return do_fork(SIGCHLD, regs->rsp, regs, 0, NULL, NULL);
}

asmlinkage long sys_clone(unsigned long clone_flags, unsigned long newsp, void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
   
	if (!newsp)
		newsp = regs->rsp;
	return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}

什么是内核抢占?系统中有哪些内核抢占的时机?

什么是内核抢占?

首先介绍非抢占式内核:当进程在内核态执行时,它不能被任意挂起,也不能被另一个进程代替。

而对于内核抢占,可以这么说:

如果进程正在执行内核函数,即它在内核态运行时,允许发生内核切换(被替代的进程是正执行内核函数的进程),这个内核就是抢占的。

注意,非抢占式内核页可以自动放弃CPU,从而进行进程切换,比如进程由于等待资源而不得不转入睡眠状态。

这种切换称为计划性切换。抢占式内核和非抢占式内核不同的切换方式是另一种,在响应引起进程切换的异步事件(例如唤醒高优先权进程的中断处理程序),这种叫强制性进程切换。

总结起来就是,抢占内核的特点为,一个在内核态运行的进程,可能在执行内核函数期间被另一个进程取代。

内核抢占的时机有哪些?

  1. 一个进程在执行异常处理程序时(肯定是内核态),另一个具有较高优先级的进程B变为可执行状态。比如发出了中断请求而且相应的处理程序唤醒了进程B。

  2. 一个执行异常处理程序的进程已经用完了它的时间配额。(只有当内核正在执行异常处理程序(尤其是系统调用时),而且内核抢占没有被显式禁止时,才可能抢占内核,此外,本地CPU还必须打开本地中断,否则无法完成内核抢占)

  3. 结束内核控制路径(通常是一个中断处理程序)时发生。

  4. 也可能是在异常处理程序调用preemt_enable()重新允许内核抢占时发生。

  5. 还可能发生在启用可延迟函数的时候。

Linux内核有哪些同步机制?他们的区别是什么?哪些同步机制可以用于中断处理函数?

技术 说明 用途 局限 备注
每CPU变量 数据结构的数组,每个CPU拥有数组的一个元素 为来自不同CPU的并发访问提供保护 对来自异步函数(中断处理程序和可延迟函数)的访问不提供保护 必须禁用抢占
原子操作 操作必须以单个指令执行,中间不能中断,且避免其他CPU访问同一存储器单元 避免“读-修改-写”指令引起的竞争条件
内存屏障 避免指令重新排序 还不太懂
自旋锁 加锁时忙等
信号量 加锁时阻塞等待(睡眠)
顺序锁 基于访问计数器的锁
本地中断的禁止 禁止单个CPU上的中断处理
本地软中断的禁止 禁止单个CPU上的可延迟函数处理
读-拷贝-更新(RCU) 通过指针而不是锁来访问共享数据结构

为什么每CPU变量必须禁用抢占?

假如不禁用抢占,就会发生以下情况:
一个内核控制路径如果获得了它的每CPU变量本地副本的地址,然后它被抢占,之后又被转移到另一个CPU上,这时它仍然引用原来CPU元素的地址。

获得自旋锁后可以发生内核抢占么?为什么?

不能。从效率看,因为如果有其他进程在等待锁,那么当前占用自旋锁的进程被抢占会导致等待锁的进程忙等,会浪费大量资源。

事实上,适合使用自旋锁的内核资源都很锁很短的时间,与其马上抢占,不如等待释放自旋锁后再抢占。而且如果有进程等待时间过长,占用自选锁的内核进程也会提前释放锁。

内核是如何维持墙钟和单调时钟的?系统睡眠唤醒对单调时钟是否有影响?设置系统时间对单调时钟是 否有影响?

Linux中的高精度定时器和低精度定时器分别是如何组织的?

malloc()分配的物理内存是连续的吗,kmalloc()呢,为什么?

malloc()函数是对进程中的堆(一个特殊的线性区)进行内存分配的函数。
malloc(size)请求size个字节的动态内存。如果分配成功,就返回所分配内存单元第一个字节的线性地址。
malloc()分配的内存的线性地址是连续的,在同一页框内的物理地址也是连续的,但是不同页之间的物理地址不一定连续。

kmalloc()分配的物理内存是连续的。因为调用kmalloc()函数实际得到的是普通高速缓存中的对象,它们具有几何分布的大小。

缺页异常处理的过程是怎样的?

do_page_fault()函数是80x86上的缺页中断服务程序。
它接收以下参数:

  1. pt_regs结构的地址regs,该结构包含异常发生时的处理器寄存器的值
    可以通过cs寄存器的CPL字段判断异常发生时处理器是用户态还是内核态
  2. error_code,当异常发生时,由控制单元压入栈中。

do_page_fault()的第一步是读取引起缺页的线性地址。当异常发生时,CPU控制单元把这个值存放在cr2控制寄存器中

然后缺页异常处理会进行一系列判断,具体流程如下图:
在这里插入图片描述

echo m > /proc/sysrq-trigger可以将内存信息比较详细的输出到dmesg中,请解读

/proc/sysrq-trigger文件通过/drivers/tty/sysrq.c文件生成。
首先是模块注册。

static int __init sysrq_init(void)
{
   
	sysrq_init_procfs();

	if (sysrq_on())
		sysrq_register_handler();

	return 0;
}
module_init(sysrq_init);

然后看sysrq_init_procfs()如何实现。如果带参数CONFIG_PROC_FS,那么就初始化,否则不做任何事情。

#ifdef CONFIG_PROC_FS

static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,</
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值