Browsed by
标签:PHP

[Linux 0.11] Draft 4 GCC Assembly

[Linux 0.11] Draft 4 GCC Assembly

全文参考 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Extended-Asm

阅读文档永远比阅读博客能获得更多的信息哦~

本文对 GCC Assembly 进行总结和介绍,也是个人的笔记,欢迎各位指正,内容供参考

* Basic Asm — 基本不能用的 assembly
* Extended Asm 功能强大的可扩展的ASM

Read More Read More

[Archlinux] 如何解决搜狗输入法炸掉的问题

[Archlinux] 如何解决搜狗输入法炸掉的问题

近期不知道什么蜜汁更新导致fcitx-sogoupinyin可能由于魔幻的力量崩溃。而且看错误毫无头绪(炸在奇怪的glibc的函数里)

因此这里提供一个玄学尝试的办法

rm ~/.config/SogouPY

pgrep fcitx | xargs kill

再启动fcitx

以及sogou-qimpanel

也许就能救回来哦~

【又水了一篇文章】

[Linux 0.11] Draft 2 80386 System Architecture

[Linux 0.11] Draft 2 80386 System Architecture

此文对 Intel 80386 的系统架构进行一定程度的说明

80386 在系统层面支持下面的功能

  • 保护
  • 内存管理
  • 多任务
  • IO
  • 异常和中断
  • 初始化
  • 协处理器,多处理技术
  • 调试功能

上述均通过系统寄存器以及指令来支持,下面会对这些进行介绍

系统寄存器(Registers)

系统寄存器分为如下几类

  • EFLAGS
  • 内存管理寄存器
  • 控制寄存器 (Control Registers)
  • 调试寄存器 (Debug Registers)
  • 测试寄存器 (Test Registers)

下面对每一类寄存器进行说明

System FLAGS

EFLAGS 中的 System FLAG 可以控制如 IO, 可屏蔽中断,任务切换,调试等功能。

  • IF: 用来控制系统是否接受(可屏蔽的)外部中断请求 IF = 0 为屏蔽
  • NT: 此位用于控制 Chain interrupted 以及 called tasks, 该位会影响 IRET 指令的操作方式
  • RF: debug 有关的 flag
  • TF: debug 有关的 flag,将处理器设置为单步执行模式,在这个模式下,所有的指令CPU都会产生一个异常,可以在每一个指令处停下来进行调试
  • VM: 用于进入 Virtual 8086 Mode 的 flag 这里不做解释

内存管理寄存器

与内存分段管理相关的寄存器,如全局描述符表,中断描述符表,也就是 GDT, LDT, IDT 和 Task 的描述信息存储用的寄存器

  • GDTR
  • LDTR
  • IDTR
  • TR   上述三个不用介绍,这个为 Task Register,用于指向当前执行的 Task 的相关元信息

控制寄存器

控制寄存器有 CR0, CR1, CR2, CR3 其中 CR1  留作 Intel 以后使用,在80386中没有实际用途。其余三个都可以通过,而且仅可以通过 MOV 指令来进行操作,将其从通用寄存器中装入,或者存入通用寄存器

  • CR0 对整个系统控制,不针对某一个任务,CR0 可以控制协处理器相关的操作(EM, ET, MP) 以及 开启保护模式 (PE) 开启分页模式 (PG) 获取任务切换状态(TS)
  • CR2 用来处理缺页异常,CR2 中存储的是触发缺页异常的线性地址的值
  • CR3 用来指定当前任务中页表所在的地址

CR2, CR3 均是分页相关的寄存器

调试寄存器

暂略

测试寄存器

单独为了 TLB 提供支持的寄存器,TLB (Translation Lookaside Buffer) 又称内存快表

—–

下面再对通用寄存器进行说明

通用寄存器

通用寄存器分为三类

  • 通用寄存器 General Register
  • 段寄存器 Segment Register
  • 状态寄存器 & 指令寄存器

通用寄存器

  • 可以做 32bit, 16bit, 8bit的寄存器为 EAX EBX ECX EDX
  • 可以做 32bit,16bit的寄存器 EBP ESI EDI ESP

分段寄存器

  • CS Code Segment 当前代码段的地址
  • SS Stack Segment 类似上文
  • DS Data Segment
  • ES  FS GS  同样为 Data Segment

状态和指令寄存器

  • EFLAGS 存储着状态
  • EIP 为指令寄存器,指向要取的指令

Stack 操作

Stack 操作使用的寄存器为 SS, ESP, EBP 分别介绍他们的作用

这里先来介绍一下 80386 CPU 对Stack的实现

Stack 在内存中实现, 指定不同的 Stack 的时候依靠切换 SS 寄存器 ( SS 寄存器指向内存段)

ESP 指针指向当前栈顶

EBP 指向该栈栈底,EBP 用来引用栈内的参数如 EBP + 4 这种

 

 

[Linux 0.11] Draft 1 Assembly

[Linux 0.11] Draft 1 Assembly

REP前缀

对 源 和 目的 地址的数据进行某些操作

汇编指令形式为 rep <INSTRUCTION>

INSTRUCTION 可以是 mov 系列(movb, movw, …) 也可以是 movs 系列,  还可以是cmps系列

该指令涉及到的寄存器有 标志寄存器(DF位) ECX ESI EDI

ECX是计数器,用来指定操作的次数 ESI 指定源地址 EDI 指定目的地址 (很好记辣 Source Index (SI) Destination Index (DI))

当处理 movs 的时候,如果 DF = 0 就是表示 ESI 指向要复制的块的开头,EDI 指向目的块的开头,DF = 0 的时候这两个就变成指向末尾了

 

JMPI 间接跳转

在实模式下,指定段地址和偏移量,将会设置IP以及CS

磁盘参数表

磁盘参数表存在中断向量0x41中 第二个磁盘的参数表存在0x46中

 

开启A20地址线

A20地址线关闭状态县 限制最多仅能寻址到1MB空间,更大的空间将会循环

开启方法如下:

 

字符串比较指令

SCAS, SCASB, SCASW, SCASD

AX, ES, DI FLAGS(DF)

比较 AL(AX)(EAX) 与 ES:[DI] 的值,不保留结果,仅保留FLAGS。

根据后缀(B,W,D)的不同,比较的字节数也不同

根据方向位(D FLAG)决定比较之后DI寄存器是增长还是减少

可以与REP前缀一起使用

设置页表

页表设置只需要用到

CR3寄存器 (页表的物理地址)

以及CR0寄存器(开启PG Flag)

任务切换相关

STR , LTR 装载,保存 Task Register (参数为段选择符)

 

ArchLinux on MBP Installation Guide [大部分内容适合普通ArchLinux安装] 上

ArchLinux on MBP Installation Guide [大部分内容适合普通ArchLinux安装] 上

TL;DR

这两天OS X的虚拟机挂了,两个月的工作成果都丢了,因为本人是重度Archlinux依赖用户既然虚拟机这么难用(不能用全部内存&有快照损坏的风险)因而,唯一的选择就是在MBP上装Archlinux了。同时因为这是第一次用UEFI的模式Dual boot Archlinux + OS X,并且是第一次使用KDE而不是一直用的Gnome,因而踩了很多坑,所以本文会比较长(

本文介绍的是在MBP上Dualboot OS X + ArchLinux 使用KDE并配置好所需的必备软件的整个过程

ArchLinux 优点

如果连这个都不知道的话那么说明可能温豆师/污班图还是比较适合你((其实是很麻烦不想写因为已经写烂大街了

那么我们就开始吧OwO

材料准备

  • 一个容量足够装下ArchISO的U盘
  • 一台Macbook
  • 畅通的网络
  • 一些干粮(不然熬久了会饿的)
  • rEFInd
  • 不确定是否要禁用Apple的 Configuring System Integrity Protection 如果在操作引导过程中遇到问题,那么就设置一下这个
  • 足够大的空间(用OS X 自带的 Disk Util 将要用来装Arch的空间划分出来,不用管文件系统,反正一会儿也得删)

引导安装

EFI 介绍

EFI boot是比 BIOS Boot 要先进的 boot 方式,古老的 BIOS 需要让CPU先进入16bit的实模式,仅仅能执行有限的一些 BIOS 提供的中断,并且 BIOS因为是直接用CPU的汇编编写的,对硬件平台有非常高的依赖,包括在BIOS下运行的驱动,尤其网络驱动,还要独立的给每一个架构的CPU编写一套独立的驱动,维护难度和开发难度都比较高,而 EFI 加载的驱动是以 EFI 字节编码 ,独立于CPU架构,因而更优,且BIOS无法支持大于 2TiB 的硬盘,因而对目前的很多机器这都是致命的瓶颈 (参考资料), 因而目前的个人PC开始采用 UEFI (EFI的一个更新版本)进行系统的引导。

引导过程以及ESP

EFI 的引导不同与 BIOS,不需要将 Bootable program 放在 first sector ,而是将引导程序放在 ESP 中

ESP (EFI System Partition) 存放了EFI引导的必备程序,路径格式需要符合此规范<EFI_SYSTEM_PARTITION>/BOOT/BOOT<MACHINE_TYPE_SHORT_NAME>.EFI (此行摘自wikipedia)

例如/efi/BOOT/BOOT (具体识别哪个路径,以及能否识别不同的路径跟固件的实现有关,具体讨论如下

 

识别之后,就会加载相应的程序以及驱动,之后就是.efi程序接管启动过程了

操作

这里采用的是使用rEFInd替换OS X原生的引导。

首先呢我们要将引导准备好, 以下操作在OS X下进行

rEFInd 下载安装程序, 安装好rEFInd

http://www.rodsbooks.com/refind/installing.html 这里的教程应该是有点过时了,安装 refind 现在可以简单的直接运行refind-install指令

refind-install 会自动识别一般的Mac的 EFI 分区并且将refind<arch>.efi复制进去

然后需要修改OS X的默认boot loader,这里使用 OS X 提供的 bless 工具即可  (如果无法写入的话,关闭System Integrety Protection) 

sudo bless --mount /Volumes/ESP --setBoot --file /Volumes/ESP/efi/refind/refind_x64.efi --shortform

然后关机,再次开机之后应该就能看到rEFInd的界面啦~

下一步就是开始进行ArchLinux的安装辣~ 请各位小伙伴准备好ArchLinux的wiki,查看Installation Guide与Macbook这两个词条哦~

基本安装

首先制作好LiveCD,LiveCD的制作和在其他电脑上安装 Archlinux 没有区别,然后重启Boot到ArchLinux LiveCD中即可

然后下面开始ArchLinux安装的基本步骤:

调网络,时间=>分区=>格式化=>挂盘=>装底包=>chroot=>各种基本配置=>装引导=>重启

为了照顾到安装还不熟练的小伙伴们,这里会把每个过程都列出来(其实我也不熟练,也就装了十几次(逃

网络和时间设置

考虑到Macbook大多数没有有线以太网口,这里以无线网链接为例来进行介绍,我们使用wpa_supplicant进行处理。

首先检测自己的网络设备

ip link list  找到自己的无线设备,然后将设备设置为up状态

ip link set <dev> up

然后使用wpa_supplicant链接无线网络,具体用法如下

wpa_supplicant -Dnl80211,wext -i <dev> -c<(wpa_passphrase "YourNetWorkSSID" "YourNetworkPass")  这里解释一下该指令的含义,wpa_supplicant 是用于进行WiFi认证的客户端,指定的参数为:驱动使用 nl80211 或者 fallback 为 wext 驱动(前者为新的netlink interface驱动),使用的网络接口设备为 <dev> ,链接用的配置文件从重定向读入,重定向是一个shell指令,wpa_passphrase用来生成可以被wpa_supplicant读取的配置文件。

测试如果可以链接之后,Ctrl+C 停掉进程,加上-B参数以后台模式运行wpa_supplicant。

下面获取IP,推荐WiFi开启dhcp服务,之后直接通过 linux 的 dhcpcd client 获取 IP 即可

之后就可以尝试

ping archlinuxcn.org 

分区

窝们采用 UEFI 在MBP上 Dual Boot OS X & ArchLinux ,因而分区的创建要注意几点

  •  第一个Archlinux的分区要和OS X分区有128MB的空隙,不然会导致OS X无法正常使用
  •  设置好 UEFI 分区的GUID

分区大家可以使用自己喜欢的工具进行,这里要注意的就是,给Arch分第一个分区的时候要在First Sector中填入+128M(有的工具不支持,窝只在cgdisk里用过) ,然后注意EFI分区大小要大于200MB,并且设好GUID,其他分区自行判断辣~

这里给出本人的一个分区方案

格式化

窝采用了 xfs 作为文件系统, 过一阵子考虑做一个对不同文件系统的对比,介绍的文章(坑)。因而将/home /var / 全部格式化为 xfs格式,把  /boot 格式化为 vfat 格式, 这里给出格式化/boot分区的指令

mkfs.fat -F32 /dev/sdxY

格式化好之后就可以挂盘开始装基础包辣~

挂载硬盘(/boot的挂载是重点)

这里要注意,窝们使用的是 UEFI 的方式 boot 系统,那么要配置好 ESP (EFI System Partition) 使之符合 ESP 的文件目录规范~不过这里说是规范呢,实际上是和具体实现有关的,好在Mac上的(至少窝的macbook pro 15)firmware会遍历/efi下的*.efi文件,找到了就会以此作为boot loader 或者 bootable device (具体寻找顺序,未知,待实验)

OS X已经有现成的 ESP 了, 我们不妨就直接拿来用, 将 apple 的 ESP (在我电脑里是 /dev/sda1)挂载到 /boot/efi 上,在此之前先挂载好 /boot ,其他盘的挂载方式都按照wiki来即可,挂载在 /mnt 下,顺序为先挂 / 的分区,然后挂 /home /var /boot 然后在 /boot 里创建 efi 挂载点,把 esp 挂载到 efi 挂载点上,挂载之后的样子应该和窝上面给出的格式类似。

挂载好之后,并且确定网络正常,就可以开始正式的安装了~

装底包

在安装前先选择好速度较快的镜像, 修改 /etc/pacman.d/mirrolist 将想要使用的镜像放到最前面,即可

然后就可以  pacstrap /mnt base 了~ 这个过程结束之后, archlinux 的基本包就装好了~

chroot & 基本配置

在chroot之前,我们来建立一下开机必备的fstab文件。

fstab 介绍

fstab 是文件系统的静态信息(翻译自man page) 简单理解这个信息就是系统启动的时候对磁盘分区的挂载的指示,如果没有此信息或者此信息存在错误,那么很可能因为磁盘挂载失败导致开机失败。

以下是一个 fstab entry 的示例

用空格和tab将一个entry分为了多个部分:

  •  第一部分是 用于标示分区的部分,可以是分区名(/dev/sda2),也可以是分区 UUID (UUID=xxxx),或者label,或者GPT分区的UUID,Label 不过强烈不建议使用分区名作为分区的标示依据,分区名会根据连接的设备不同而变化,比如目前是/dev/sda的硬盘可能在你连了一个移动硬盘之后就变成/dev/sdb了,这样以来fstab就全乱了
  • 第二部分是挂载点
  • 第三部分是文件系统类型
  • 第四部分是mount时候的选项,比如 ro 只读挂载,还有特定文件系统自带的一些选项
  • 第五部分(未知)
  • 第六部分是mount的顺序,编号越小越先挂载

好了,介绍完fstab了,那么现在我们就来生成fstab,注意一定要在生成的时候加上 -U 选项不然的话生成的就不是以UUID标示分区的fstab,而是以分区设备名标识的了(惨痛的教训

然后接下来就可以chroot了~

之后就是一些必要的设置,以及引导的安装啦

首先我们设置一下时区, 并生成 /etc/adjtime

之后设置必要的语言环境(locale),如果设置语言环境出了问题,就会出现各种神奇的乱码,让你欲罢不能(很烦

国内的用户需要用到的locale应该至少有 en_US.UTF-8 zh_CN.UTF-8 这两个,所以我们把这两行从 /etc/locale.gen 中解除注释 然后生成 locale

在 /etc/locale.conf 中设置好默认语言LANG

然后建立一个 /etc/hostname 文件,里面给自己心爱的 Arch 酱起个名字吧~~

不要忘了给root用户设置一个密码,下一次登录要使用

好啦,经过这些设置之后,我们已经有一个基本功能的 Archlinux 了,下面我们要 Boot 到安装好的 ArchLinux 中,离开 LiveCD,在这之前,我们还有一些事情要做~

 

安装引导程序

refind 需要一个config file来 boot 我们的ArchLinux,这个configfile可以通过 refind-install 生成一个框架,然后我们需要修改一下这几个entry,因为默认生成的 entry 不能用(((

使用refind-install之后,我们应该能在 /boot/ 下看到一个文件 refind_linux.conf ,没错就是他,而且他应该和vmlinuz-linux以及initramfs在一个分区下才正确 。

我们打开这个文件,修改它的内容如下~

恩恩~~ 那么只要重启,就能(应该)看到我们的Arch Linux辣~ 我们将会在真正的ArchLinux而不是ArchISO里进行后续的图形界面设置,以及自定义设置

 

不过在此之前,为了下一步能够进行下去,我们需要安装无线网络工具,比如 netctl 或者 刚刚使用的 wpa_supplicant, 这两个包是没有随着 base 一起装进来的。

 

那么我们这次就说到这里~ 下一篇文章将会介绍详细的设置,以及 MBP 的几个常见使用问题的解决办法

Eudyptula Challenge 1 — 8 总结

Eudyptula Challenge 1 — 8 总结

TL;DR

(PS: 昨天竟然在一个非计算机领域的朋友口中听到TLNR好神奇)

从开始做eudyptula-challenge已经有一个多月了,也从原来的连第一关都会卡关好久到现在第八关只用了不到四十分钟的时间就写好了(虽然第八关的coding要求很简单,主要是要练习git send-mail的使用方法)随着Challenge的进行,之前关卡学到的一些东西难免会遗忘,因此在继续进行之前,现将前面学到的知识进行总结。注意: 本文不是Eudyptula Challenge的攻略,Eudyptula Challenge明确禁止了对答案代码的公布和分享,个人也赞同这种做法,毕竟是Challenge,需要你自己研究,而不是直接上网就能搜到答案(尽管现在的确能搜到答案了,比如某章鱼猫((

Intro

What is Eudyptula Challenge?

这里就引用官网的介绍了

The Eudyptula Challenge is a series of programming exercises for the Linux kernel, that start from a very basic “Hello world” kernel module, moving on up in complexity to getting patches accepted into the main Linux kernel source tree.

How does it work?

官网上也给了相应的介绍,不过窝认为从一个例子来介绍更为形象:

Eudyptula-Challenge的评判系统是由”A set of convoluted shell scripts that are slow to anger and impossible to debug.”(摘自官网)来进行的,也就是全部自动评测(不过有的时候它的只能程度让我猜想后面有人在操作*****(大雾

以一个Task为例, 当你注册成功Eudyptula Challenge之后,会收到一封说明邮件以及你的第一个任务,之后你所有的任务提交都将通过在第一个任务里给你分配的ID以plain-text mail的形式进行。

当你收到一个Task的时候,根据相应的知识完成任务要求的代码之后,你需要通过纯文本邮件客户端(如mutt)将proof运行结果的输出作为正文,将写好的代码作为附件(这里不能用gmail的plain-text模式,因为它会把邮件附件转为base64格式)通过客户端发送给little@eudyptula-challenge.org,之后你会收到一封通知邮件,表示系统已经成功收到你的提交(有可能收不到,因为脚本也许会卡住,这时候你如果等了一天还没收到,那你可以再次发送一封,不要频繁重发,小企鹅会生气的(不要问我怎么知道的Orz))

通知邮件的格式是这样的

可以看到上一个最近处理的提交时间,以及当前你所在Task的队列里有多少人在你的前面,一般来说从Task01-Task08都是平均一天之内就会回复你的Submit的结果,除非是脚本出故障了(

然后只需要等待结果,在此期间你可以看看番,学学其他有趣的东西比如日语,比如机器学习( “There is a lot more
in life other than Linux kernel programming.”

结果到达之后,如果你成功通过这个Task,那么会收到Congrats以及下一个Task的任务邮件,如果你没有通过,你会收到具有一定指导信息的来自小企鹅的建议(代码逻辑错误,不够精简,格式不正确,etc)

以上就是Eudyptula Challenge的一个完整的流程~ 那么下面开始总结

Task 01

主要掌握了编写module Makefile的方法,以及module的最最最基本的骨架, init函数和cleanup函数 ,以及如何设置mutt客户端(这个好像是弄得最久的?) 关于mutt客户端的设定方法,参考我的上一篇文章, 关于simplest module的编写 参考本文即可 http://www.tldp.org/LDP/lkmpg/2.6/html/x121.html

Task 02

编译内核,使用menuconfig, config或者xconfig设置内核参数,编译自定义的内核,了解了linux-git tree 以及 linux version的命名方式 参考 https://www.kernel.org/doc/html/latest/process/howto.html 中的kernel developing process

在这里简述一下kernel develop的过程

  • 当某一个4.x版本发行之后,会有两周的窗口期,这个窗口期内可以提交大的修改,以及feature patch,通常情况下,这些patch都已经在 -next tree里存在了几周
  • 两周窗口期后,-rc1 版本会发布 正如其名, make the kernel as rock solid as possible( rc = rock ) 进入到rc1的patch应当修复regression问题 [ 4.10-rc1]
  • 当Linus认为当前代码tree已经处于可以进行充分测试的状态的时候,那么将会发布新的-rc版本,大约一周会发布一个-rc版本 [4.10-rc8]
  • 大概经过6周的发布过程,内核变得稳定。[4.10 发布]

编译内核之后,提交dmesg和uname -a信息作为任务验证信息即可

 

Task 03 – 04

如何提交Patches https://www.kernel.org/doc/html/latest/process/submitting-patches.html 

内核代码风格

Task 05

编写USB热加载的模块 要求编写一个模块在USB键盘插入到机器的时候自动加载此模块

参考内核的相关文档(writing_usb_driver.tmpl) 如果想要实现hotplug的话,需要建立一个MODULE_DEVICE_TABLE,它用于告诉内核,这个module支持相应的设备(提供vendorID & DeviceID)

之后需要实现一个用于热加载的函数probe,然后就可以了

 

Task 06

这里要求实现一个misc character device了, 要求这个device能够进行读写,并且执行相应的读写之后的处理逻辑。

这里要求学会如何注册misc_device, 以及了解file_operations结构体,并且为misc_device编写read write两个file_operation

内核内存区和用户内存区不能进行直接共享,需要通过copy_from_user以及 copy_to_user这两个函数进行内核和用户内存的拷贝

另外有两个非常方便的wrapper函数 simple_read_from_buffer simple_write_to_buffer

Task 07

编译 linux-next kernel,不过这里被一个kernel的bug卡住,编译好的新kernel启动的时候hang住

Task 08

将Task 06写的device放到debugfs中,而不是/dev下,要求实现并发 并要求使用git send-mail发送patch

debugfs创建文件和dir均很简单,在此不进行阐述。

为了实现并发,需要信号量或者是自旋锁。这里使用了rw_mutex (多写多读)。

关于自旋锁,信号量之后会考虑写文章进行解释

然后就是git send-mail这里要注意在format-patch的时候就加上的几个参数

--subject-prefix  加上ID prefix

--in-reply-to 第一封邮件的MessageID

--thread 以thread的形式发送

关于git send mail的配置不在这里进行说明,可以很容易的找到相应的配置方法

 

 

Gmail + Mutt 报错 no authenticators found 解决办法

Gmail + Mutt 报错 no authenticators found 解决办法

初步测试是配置文件错误导致的,这里要注意几个配置不要填错

下面是一个可用的配置

注意: smtp_url 要填写 smtps://USERNAME@smtp.gmail.com 而不是下面的其中一种

  • smtps://USERNAME@gmail.com@smtp.gmail.com:465
  • smtps://USERNAME@smtp.gmail.com:587
  • 。。。

填写正确之后, 可以使用mutt进行邮件的发送

 

IO 多路复用 — select 和 poll

IO 多路复用 — select 和 poll

本文为 高性能网络编程学习 系列文章的第2篇

下面我们使用select, poll, epoll来进行IO multiplexing 本文我们演示 select 和 poll做IO多路复用

select的用法查看man page就可以得到,这里只列出遇到的问题

  • nfds参数,表明要watch的最大fd的数字 举例说明,如果你要watch的文件描述符为24 — 31,那么你应该将nfds设置为32
  • 为了防止SIGPIPE信号终止程序运行,要讲SIGPIPE信号忽略掉

代码如下

进行到这里的时候遇到了一个很费解的问题,server端在读取数据的时候会收到recv(): connection reset by peer这样的错误,使用tcpdump抓包查看后,发现client关闭链接的时候(cli.Close)并没有发送FIN给server端,而是直接关闭了链接,这时server再请求读取client的数据或者发送数据给client就会收到cli发来的RST(connection reset by peer) 关于这个问题的具体产生原因,目前有一个原因

  • 当客户端关闭链接的时候内核network buffer内还有数据,则不会发送FIN,而是发送RST

其他的原因还在调查,待定

Linux Kernel Development Resources

Linux Kernel Development Resources

Here are some resources for digging into linux kernel development (Keep updating)

Books

  • Understanding Linux Kernel [ULK]
  • Linux Kernel Development [LKD]
  • Linux Driver Development [LDD]
  • Linux Kernel Module Programming Guide [LKMPG]
  • Linux in a Nutshell

 

Sites

 

ULK Chapter2 总结

ULK Chapter2 总结

本章的知识结构如下

地址转换过程

谈到内存地址,具体来讲有三种不同类型的地址,逻辑地址,线性地址,物理地址。

  • 逻辑地址对应内存的分段
  • 线性地址对应分页
  • 物理地址对应到硬件芯片上的内存单元的地址

MMU通过Segment Unit与Paging Unit两个硬件电路将一个逻辑地址转为物理地址, 具体转换过程如上代码段所示

下面对整个转换过程进行概述

段选择符

[0-1]RPL: 请求者特权级

[2]TI: Table Indicator, 指明是从GDT还是LDT中取出段描述符(Segment Descriptor)

[3-15]index:用来指定从GDT中第index项取出Segment Descriptor

段描述符

对段描述符的解释参考 [不是科普向?] RE: 从零开始的操作系统开发 第二集 中相应内容即可

Logical Addr ===> Linear Addr

逻辑地址的高16位为段选择符(Segment Selector)其余32位(或者64位)为偏移量(Offset)

流程如下

  1. 检查TI确定是从GDT(TI=0)还是从LDT(TI =1)中选择段描述符
  2. 从Segment Selector的index字段计算出段描述符的地址,计算方法 index * 8 (一个Segment Descriptor大小为8) 并与gdtr/ldtr中的内容相加得到Segment Descriptor
  3. 把逻辑地址的offset字段与Segment Descriptor中的Base字段相加,得到Linear Address

 

而Linux中的分段只是一个兼容性考虑,分段和分页的作用是重复的,分页能以更精细的粒度对内存进行管理,因而在Linux中分页是主要的手段,Linux分段中的用户代码段,数据段,内核代码段,数据段,都是以Base = 0 ,因而Linux下的逻辑地址和线性地址是一致的,(因为Base = 0 所以 Linear Address = Base + Offset = Offset) 即逻辑地址的偏移量和线性地址的值是相等的

 

Linear Addr ==> Physical Addr

至此我们已经得到了线性地址,下面需要将线性地址通过分页单元转为物理地址,下面以基本的分页模型对分页进行解释

80×86的常规分页结构为

页目录 页表 页框

如图

如图,Page Directory和PageTable的结构类似,都是由一些flag bit加上Field字段,Field字段表示页框的物理地址,如果是一个PageTable的话,那么这个页框就含有一页数据,Page Directory的话,页框内含有的是一个页的PageTable(页表的大小就是一个PageTable)

 

寻址的方式如下,首先根据CR3寄存器的内容,找到PageDirectory入口的物理地址,然后加上Linear Addr中的DIRECTORY,找到对应的PDE项,并根据此内容找到对应的Page Table的物理地址,并根据Linear Addr中的TABLE找到相应的Page即页框的物理地址,最后将此物理地址加上Linear Addr的OFFSET字段,最后得到物理地址

 

而在64位系统上,如果依旧采用这种最基本的分页结构的话,那么假设页框大小为4K,所以OFFSET位依旧为12bit,然后其余的52bit可以分给PT(Page Table的简称,下同)和PD(Page Directory的简称,下同),这样就会导致我们每个进程的页目录和页表变得非常非常多,超过256000项

因为这个原因,所以对于64位处理器的分页系统,都使用了更多级别的分页级别,如x86_64使用48位寻址,分页为4级线性地址分级为 9 + 9 + 9 + 9 + 12 如下图所示

原理都是一样的,只不过分页级别变多了

以上就是Logical Address ===> Physical Address的转换的大致流程

 

缓存技术

因为CPU Register的读取和内存的读取速度相差甚大,为了缩小这个差距,避免CPU等待过长的时间,使用缓存技术来将内存中的部分数据缓存在高速静态RAM(SRAM)里,即为高速缓存技术(Hardware Cache)

此外,将Logical Address转换为Physical Address也需要多次进行内存的读取(查找各级页表),为了加速此过程,引入了转换后援缓冲器Translation Lookaside Buffer(TLB)技术

Hardware Cache

对高速缓存的原理描述超出本文的范围,这里仅对高速缓存的读写方式进行一定说明:

在更新内存数据的时候,是同时更新高速缓存和内存的数据,还是仅仅更新缓存的数据,根据这个有两种不同的写策略

  • 通写(write-through): 既写RAM也写缓存
  • 回写(write-back): 只写缓存,只有当需要FLUSH的时候才更新RAM

操作系统可以针对不同的页框配置不同的告诉缓存管理策略,通过PCD位和PWT位

  • PCD表示是否对此页框启用告诉缓存
  • PWT表示是否采用write-through的策略

在Linux下,对全部的页框都启用告诉缓存,并且都采用write-back策略

TLB(快表)

TLB的作用是将Logical Address对应的Linear Address缓存起来,以加速对内存的访问

但是这个缓存是有失效期的,最明显的一个失效就是当PD都被替换了的时候,也就是cr3的寄存器内容更新了,硬件上,当cr3更新的时候,TLB的内容会自动失效

如何能够更好的利用TLB来加速访问是对Linux性能影响十分重要的一部分,为了避免多处理器系统上无用的TLB刷新,内核使用一种叫做Lazy TLB的技术,关于Lazy TLB技术的具体实现之后补充

不同的分页方式

上述已经对常规分页进行举例了,下面对不同的几种分页方式进行简单整理

扩展分页

80×86模型的另一种分页模式,页框大小为4MB而不是4KB,用于把大段连续的线性地址转换成相应的物理地址,因为没有了PT只有PD,节省了内存,同时PT—>Phy而不是PT–>PD–>Phy,少了一级转换效率也相应提高,

物理地址扩展(PAE)分页机制

早些时候,内存容量都很少,而当需要大内存(>4G)的服务器&程序出现的时候,上述的寻址方式就不能使用了,因而Intel将内存地址位数从32 –>36位(即将引脚从32个扩展到36个) 而之前的所有的地址转换都是从32位Linear Address转换到32位Physical Address的,需要有一种新的转换机制,将32bit linear addr->36bit phy addr,这种机制即为PAE机制,详情可以参考Intel手册 Vol3A相应的内容

Linux中的实现

对于Linux的具体处理将在之后在整理