linux学习

第一、二讲_编译内核

重点知识

编译内核、kgdb、驱动模板

课程

见文末资料,驱动模板见文末代码,视频中有详细讲

  • 编译内核的过程、需要注意的细节
  • kgdb的过程

第三讲_指针存储之谜

重点知识

  • 平坦内存模型
  • GS段per cpu含义和FS段的TLS

linux学习方法

  • 文档阅读法
  • 编写内核模块
  • 找人帮忙法
  • 调试内核模块(kgdb)
  • 反汇编分析
  • 破坏法

课程

课程知识点1:偏移量到物理地址的转换过程

int test;
int* ptr =&test

指针存储的是地址,是没有问题的。
但有多种地址,
《汇编语言》说:

  1. 逻辑地址(段基址:偏移量)
  2. 物理地址 = 段基址(段寄存器×10H)+ 偏移量

《操作系统》说:

虚拟地址经页表(多级)转换,变成物理地址
在这里插入图片描述

逻辑地址和线性地址都是虚拟地址,那么,指针中存储的是哪种?
答案是: 段基址都被存储在寄存器中,指针中只放偏移量。
疑惑: 程序员写c等代码完全不需要考虑段寄存器,直接使用偏移量(指针),程序运行顺利。偏移量如何转换成地址?
探究
体系结构中找根据,使用文档阅读法。 《Intel® 64 and IA-32 Architectures Software Developer’s Manual》的卷3《Volume 3: System Programming Guide》给出体系结构,从指针到物理地址的转换过程。
在这里插入图片描述
指针到物理地址的转换过程分为3步:

1.在GDT表中找到逻辑地址的segment selector字段对应的表项:段描述符。
2. 在线性空间中,用段描述符中的线性地址基地址+逻辑地址偏移量(即指针中的值)得到线性地址。
3. 线性地址通过页寻址找到物理地址。

如果关闭分页,线性地址就是最终的物理地址。1、2步在所有操作系统中都是必要的,3步不是必要的。

步骤1中寻找段描述符需要用到两个寄存器。
在这里插入图片描述这一步不需要指针中的信息,指针中存储的是偏移量,而这一步是寻找基址。
GDT表在内存中的位置记录在GDTR寄存器中,段寄存器的index是段描述符的位偏移量。

课程知识点2:平坦内存模型

明白转换过程,那么谁完成这个转换,编译器还是操作系统呢?
答案:操作系统
疑惑:操作系统是如何完成上述的地址转换过程呢?
探究:编写内核,读取GDT、GDTR、段寄存器,获取并段描述符。

使用程序员手段:编写内核模块进行探究。
(GDTR寄存器、GDT表只能在ring 0级别(内核态)读取,不能直接在用户态写代码,必须编写内核模块)。
代码思路:

  1. 编写用户态程序,获取其CS、DS、SS,以弄清使用的是GDT or LDT,以及段描述符在表内的index。
  2. 进入内核,获取其CS、DS、SS寄存器中的内容。
  3. 读取GDTR寄存器,获取GDT的基地址。
  4. 打印出GDT表格的内容,并找出各个段寄存器对应的表项,即段描述符。
  5. 分析段描述符内的基地址、段界限等信息。
    代码3.1(32位)
    结果分析:
    32位机用户态的CS=0x73
    在这里插入图片描述
    在这里插入图片描述
    段描述符字段含义:
    在这里插入图片描述32位机用户态的CS的段基址和段基址如下:
    在这里插入图片描述在这里插入图片描述分析用户态的DS、SS段和内核态的DS、SS、CS段类似。段基址都是0,段界限都是0xFFFFFFFF(32位机器可寻址范围)。
    即32位机器,全局一个段,等同没有分段。偏移量就是线性地址。int *ptr = &test; ptr既存储偏移量,也存储线性地址。

这种全局一个段的内存管理模型被称为平坦的内存管理模型(Flat Memory Model)。编程时不用管段寄存器(除了fs和gs段),直接使用逻辑地址代替线性地址。

不仅Linux使用平坦内存模型,windows也采用了,x64中,忽略了段描述符中的段基址和段界限。即cpu直接支持平坦模式。

课程知识点3:在GS表中的偏移地址

已经观察了描述符,我们进一步在段描述符找到后,内核的工作。
源码文件中搜索出现了lgdt指令的地方+内核源码阅读网站,看源码如何管理。最后看到
在这里插入图片描述可以知道GDT表由gdt_page这个结构体表示,在操作系统中有一个全局实例,名字也叫gdt_page。赋值过程如上图代码。
继续查看源码,发现GDT_ENTRY_KERNEL_CS这个宏就是内核态CS段再GDT表中的index,赋值使用了GDT_ENTRY_INIT,这个宏,定义比较复杂,自己手动算值挺麻烦的,使用**“找人帮忙法”**,见代码3.3。
用写驱动调用GDT_ENTRY_INIT这个宏,或者把GDT_ENTRY_INIT的定义抄到用户态代码,自己写个简单代码计算,看计算出来的地址中存储的是不是对应内核态CS段描述符。
这里的“找人帮忙法”,就是不想自己看复杂代码,写代码调用,检验自己猜想函数对应功能对不对。

进一步,我们试一试使用内核提供的结构

前面找到段描述符,是写汇编代码取GDRTR寄存器,段基址寄存器的内容,再做运算,获取段描述符地址,然后去取得的段描述符。再看内核对GDT表管理时,发现内核已经定义了相应的结构。如果直接用c代码就可以取得段描述符更加方便。
代码3.5使用gdt_page这一结构体,直接去取得段描述符。
代码3.5关键部分
代码3.5直接出错,内核demsg说不能访问内存8020。为了解决这个问题,使用**“调试内核模块”**(kgdb)的程序员手段。

内核调试仍然执行到下面一步就提示段错误,走投无路,使用程序员手段**“反汇编分析”**,反汇编出错代码。

struct desc_ struct desc = gdt_ page.gdt [4] ;

在这里插入图片描述计算赋给rax寄存器的内容0xffffffffc06a6437(%rip,程序指针)+0x3f961be9=0x8020
0x0820明显是用户态的地址,理论上讲GDT表地址应该在内核态,不然用户随意操作GDT表,太危险。这和我们的直觉不符合。而且0x8020更像是一个偏移量,而不是线性地址。
因此,猜测出错原因是访问到了无效的地址:0x8020,而0x8020不是线性地址,是偏移量。
但是前面说了,平坦内存模型中,除了GS和FS段,其他段的偏移量就是线性地址。那么说明gdt_ page这一结构体在GS或FS段中,要像正确使用,就需要在0x8020前加上正确的段基址。

代码3.8检验出GS是GDT表所在的段,并编写代码,从GS段基址+0x8020中得到正确的段描述符。

虽然得到了正确的段描述符,但是在实验时(代码3.9)发现GDTR读出的GDT表基地址,和GS段内GDT表基地址不同。坑已挖,后再填。

课程知识点4: GS与per cpu

前面说到仅GS和FS段不是平坦内存模型。
原因是GS和FS段内数据的特殊性。
在64位操作系统中:
GS段:per cpu
FS段:TLS(Thread Local Storage)
每个CPU都有自己的一个GS段,每个线程都有一个自己的的FS段。
定义一个TLS变量:

__thread int x=5;

定义和访问一个一个per cpu变量:

//定义per cpu变量
DEFINE_PER_CPU(long, gUsage) = 0;
//操作per cpu变量,i是cpu编号
for_each_online_cpu(i){
		baseaddr = __per_cpu_offset[i];	//get GS segment baseaddr
		DEBUG_PRINT(DEVICE_NAME " __per_cpu_offset %lx",baseaddr);
		offset =(unsigned long)( &gUsage);
		DEBUG_PRINT(DEVICE_NAME " offset %lx",offset);
		pUsage = (long *)( offset + baseaddr ) ;
		DEBUG_PRINT(DEVICE_NAME " per cpu address %lx",pUsage);
		totol_gUsage += *pUsage;
		DEBUG_PRINT(DEVICE_NAME " cpu_%d get time %ld",i,(*pUsage));
	}
	DEBUG_PRINT(DEVICE_NAME " get totol %ld times",totol_gUsage);

小结

  1. 内核态和用户态的CS、DS、SS段都是平坦内存模型,偏移量就是线性地址。因此指针存储偏移量就相当于存储的线性地址。
  2. 若指针所存储的地址是在FS和GS段,需要加上FS和GS段的基址才能成功访问。FS保存TLS(Thread Local Storage)变量,GS保存per cpu变量。这两类变量可以提高并发,减少cache冲突。
  3. 了解程序员手段。本文中没记录破坏法,在课上,破坏法在如下情况。宏A有三处定义,程序员不知道哪一个是我们使用的定义,破坏定义1,程序编译失败。定义1是我们使用的定义。

作业

编写一个驱动程序和一个多进程的应用程序让上述用户进程通过ioctl方式,反复进入驱动程序,而驱动程序需要统计这些进程进入的次数。驱动程序在被卸载时,需要输出总的进入次数。
要求:提高并发
做法:使用per_cpu变量
实现:在“GS与per cpu”小结中已展示。

第四讲_PageWalk

重点知识

根据页表结构手动、代码实现线性地址到物理地址的转换。

课程

见文末视频资料。

作业

第三章提到:gs表的地址有两种获取方式。
1.gdtr寄存器中获得的逻辑地址
2.使用rdmsr寄存器,从gs段获得的逻辑地址
这两个地址中寻址最后得到的都是gs表的地址,但是这两个地址是不相同的。
编写代码实现page_walk,查看两个逻辑地址对应的物理地址是否相同。

作业实现

见文末代码。

资料

视频:https://i.study.uestc.edu.cn/lkt/menu/ppt
代码:https://github.com/ltCodeW/lilin_linux

所有内容都是课堂老师的辛苦成果,本文是自己总结。如果算是侵犯老师成果,马上删除。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值