Browsed by
标签:C. Linux

[Linux 0.11] Draft 5 虚拟内存管理(mm/memory.c)

[Linux 0.11] Draft 5 虚拟内存管理(mm/memory.c)

Overview

本文介绍 32 位保护模式下的内存分页, 以及 linux0.11 对物理内存的管理。

意义

既然要使用内存分页,那么就一定有他的优势所在。我们首先来探讨一下使用内存分页的意义, 分页在于为进程提供了虚拟地址空间,通过提供的这个虚拟地址空间,我们可以做到下面的这些:

  1. 精细的权限控制粒度: 内存页大小以 4KB 为一页单位(也可以是4MB, 这里不讨论这种情况), 可以针对每一个内存页设置该4K空间的访问权限。相比分段的粗粒度精细了很多,目前的分段功能只是一个兼容性考虑,分段都是将整个0 – 最大内存空间直接映射为一个段;
  2. 可以使得进程独享地址空间: 通过切换CR3寄存器的内容,可以实现多个进程占用同一个虚拟地址空间。
  3. 有效的解决了进程对大块连续内存的请求: 这里用一个例子来说明一下。

例子是这样的: 如果我们有16MB的物理内存,现在有进程A B C分别占用5MB, 2MB, 3MB内存, 且不共享内存。然后现在进程B退出了,我们还剩余8MB内存,可是如果不使用分页的话,内存连续分配, 我们现在没有办法分配出一个连续的8MB的空间了,只能分配最多最多6MB连续的内存空间, 如下图所示。

而现在存在分页,虚拟地址,我们完全可以分配连续的8MB的虚拟地址空间,其中2MB映射到物理内存的中间那个2MB空隙,其余6MB映射到最后剩余的6MB。

由此可见,分页是十分必要的,那么下面就来介绍一下如何实现分页&虚拟内存管理

地址转换过程

我们先来看一下虚拟地址是如何转换为物理地址的 完整的转换过程为

虚拟地址 --> (GDT) --> 线性地址 --> (Page Table) --> 物理地址

关于VirtualAddr -> LinearAddr 的过程我在之前的文章中有过介绍,这里就不做说明,想要跳过那个文章的读者可以简单的理解为目前的虚拟地址和线性地址值是相同的,因而我们直接从线性地址出发。首先来看下图

 

(图片来源:Intel® 64 and IA-32 Architectures Software Developer’s Manual)

线性地址为32位的地址, 其中高十位为 Page Directory,中间十位为 Page Table,最后十二位是 Offset。过程如下:

  1. 根据 CR3 寄存器(忘记了寄存器的功能的话戳这里)找到了页目录表的地址。
  2. 根据 Linear Address 的高十位与页表目录地址相加,找到了对应的页目录项(PDE)。
  3. 页目录项内存有该页目录的起始地址,将此地址与 Linear Address 21 – 12 位(即中间十位)相加,找到对应的页表项(PTE)
  4. 根据页表项中记载的物理页地址,找到对应的物理页起始地址,再与 Offset 相加,对应到实际的物理地址。

通过上面的过程就实现了线性地址到物理地址的转换,可以看出,页表和页目录在这个过程中扮演了十分重要的角色,下面就来介绍一下他们的结构。

结构

一个页目录项(PTE)和页表项(PDE)均占用 4Bytes 空间

(图片来源:Intel® 64 and IA-32 Architectures Software Developer’s Manual)

如上图所示,这是一个 Page Table Entry (页面大小为4K)

首先我们看 Bit 0 ,这是 Present bit,只有当这个 Bit 为 1 的时候此项才表示一个 PTE(或者PDE),否则就是一个无效的表项。

Bit 1 表示这个页面的读写权限,当这个位被置 1 的时候 表示可读可写, 为 0 则表示只读 ,不过这里要注意一点,对于 Ring0 且 CR0 的 WP 位为 0 情况来说,Ring0 可以对只读页面进行写入,如果想要让 Ring 0 也不可以写入只读页面,需要将 CR0 的 WP 位置1

Bit 2 表示这个页面是系统页面还是用户页面,如果是系统页面则用户态无法使用此页面。

后续的标志位暂时不介绍。我们来看高20位, 这里表示了物理页的起始地址,如果这是一个页目录项而不是页表项,则这里表示的是页目录的起始物理地址

转换缓存

通过上述的过程我们也可以看出, Page Transform 的过程需要多次读取内存,会映像执行效率,为了解决这个问题,CPU对转换信息进行了缓存,如  TLB, PCID  等技术,这些不在本文进行介绍,这里要说明的是,因为缓存的存在,导致如果更新了已被缓存的页表项的信息(如修改FLAG, 或者物理地址),我们需要使缓存失效,具体的做法就是重新装载一次 CR3 寄存器。

示例

说了这么多理论知识,我们来进行几个实际的操作看看。我们基于 linux0.11 的模型来进行下面四个实验(毕设结束后会放出git repo,目前暂无)

  • 使得某个特定的线性地址无效 (下文有介绍)
  • 将线性地址映射到给定的物理地址 (提示,查看 mm.c put_page 函数)
  • 修改该页的权限为只读并验证  (修改 FLAG 的另一个实验)
  • 给出当前线性地址所在页的详细信息 (打印出必要的FLAG信息,以及该线性地址对应的物理页地址)

disable_linear 实现

我们设计了这样的函数原型:

void disable_linear(unsigned long addr)

这个函数内我们需要对线性地址进行禁用, 根据上文中所介绍的,使得线性地址无效,实际上就是使得那个对应的页表项无效,因而我们需要通过线性地址对应到其页表项,并对该页表项的 Bit 0 清零。如何 Disable 与 Reload TLB 留给大家自行实现

linear_to_pte这个函数的实现过程实际上就是地址转换过程的前三步, 实现的时候要注意移位操作不要出问题,其他的没有什么难点

这里给出一个实现

上面的代码对页目录项是否存在,以及是否是页目录项进行了判断,失败将返回 0 。

下面我们需要对函数的正确性进行验证,由于此代码版本还没有补充缺页异常的处理代码,因而当访问失效的虚拟内存地址时,会引发Page Fault -> Double Fault -> Triple Fault最后导致我们的OS不断重启,我们来验证一下,验证用代码如下:

运行之后, 看到 qemu / bochs 的确在不停的 reboot , 并且可以通过 bochs 的 GUI 界面看到我们的页表由原有的16MB一致映射变为了中间缺少 0xdad000 页。

下面以实验结果截图结束本文

[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