简易内核开发环境

近期尝试了一下写 Linux 内核代码,于是把折腾过程记录一下,方便以后参考。本文默认使用 x86_64 架构以及 Linux 作为宿主系统。

使用 QEMU 运行 Linux 内核

在开始写代码之前,我们需要先准备好模拟器用于运行 Linux。我这里用了QEMU,你也可以用 VirtualBox 之类的虚拟机。一般来说,我们需要一个内核可执行文件(内核本体)和一个 initramfs 镜像(提供用户态程序)。当内核被加载后,它会自动载入 initramfs 镜像,并执行其中的/init程序。由于这一节的重点是 QEMU 配置,所以我直接使用宿主机的内核和 initramfs。以我的 Archlinux 系统为例,内核文件是/boot/vmlinuz-linux,initramfs 文件是/boot/initramfs-linux.img。使用以下命令启动 QEMU,按Ctrl-A x退出。

1
2
3
4
5
6
7
qemu-system-x86_64 \
-kernel /boot/vmlinuz-linux \
-initrd /boot/initramfs-linux.img \
-nographic -append "console=ttyS0" \
-m 512 \
--enable-kvm \
-cpu host
  • -kernel 指定内核可执行文件。
  • -initrd 指定 initramfs disk 镜像文件。
  • -nographic -append "console=ttyS0" 禁用视频输出并使用串口作为终端。
  • -m 512 设定内存。
  • --enable-kvm 使用KVM。
  • -cpu host 使用宿主机的 CPU 特性。

你应该能看到一些系统启动的信息以及无法挂载根分区的报错,这是正常现象,因为我们没有提供任何磁盘文件。你应该可以进入一个紧急修复 Shell, 执行一些简单的如 ls cd 之类的命令。

Haskell 简易指南

安装

用任何你喜欢的方法安装 Glasgow Haskell Compiler (a.k.a. GHC)。Cabal 之类的
依赖管理系统就用不着了。 因为我也不会用。 保证能够执行ghcghci命令就行。

GHCi基础

首先,把以下文件保存成helloworld.hs

helloworld.hs
1
foo = "hello, world"

然后执行ghci helloworld.hs,然后在>提示符后输入foo并回车

1
2
3
4
5
6
GHCi, version 8.6.3: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main ( helloworld.hs, interpreted )
Ok, one module loaded.
*Main> foo
"hello, world"
*Main>

你可以使用:r来重新载入文件,也可以使用:l <文件名>来载入代码。

编写一个简易的Chrome扩展

时隔半年的毫无诚心的流水帐作品。
假设读者有基础的Javascript能力。

起因

常去的某资源站的某资源发布者喜欢把重要的内容加上花里胡哨的特殊效果并藏在页面的角落里。
虽说要尊重资源的发布者,不过这种给人添堵的行为实在令我感到不爽,于是研究了一下Chrome的扩展程序(Extension)。

基本思路

要干的事情有两件:

  1. 将内容移到页面的显著位置
  2. 去掉辣眼睛的特殊效果 好吧,其实这条并不重要,毕竟内容已经被移到显著位置了

很自然的,直接用Javascript操纵DOM树即可实现希望的效果。
那么要怎么自动载入脚本呢?

借助IPsec和strongSwan建立隧道并分配IPv6地址

准备

在一年前,我写过一篇文章,介绍利用GRE隧道将一台服务器的IPv6地址“分配”给另一台电脑,令其能访问IPv6网络的方法。
不过那种方法存在一些问题:

  • 不能通过NAT
  • 数据不加密
  • 需要在服务器手动更新IP

于是热爱折腾作死的我研究了一下使用IPsec配合IKEv2对流量进行加密的方法。

服务器与本地均为ArchLinux(Arch大法好),strongSwan软件包可从AUR安装。
服务器需要至少有一个公网IPv4和一段Routed IPv6 Subnet。

Linux环境TCP Socket与Epoll使用备忘

流水帐式地记录了 Linux 下 TCP Socket 通信的方法和基本的 Epoll 使用方法。
没有错误处理。

地址解析

1
2
3
4
struct addrinfo *listen_addr; //存放解析结果。参见`man getaddrinfo`
getaddrinfo("0.0.0.0", "55553", NULL, &listen_addr); // getaddrinfo([主机名],[端口],[hint],[结果])。成功返回 `0`
// ...
freeaddrinfo(listen_addr); //释放资源,返回void

监听

这种方式只能同时处理一个连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fd = socket(AF_INET, SOCK_STREAM, 0); // int socket(int domain, int type, int protocol); 参见`man 3 socket` 创建文件描述符, 出错返回-1
bind(fd, listen_addr->ai_addr, listen_addr->ai_addrlen);
// int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 绑定地址,出错返回-1,参见`man 3 bind`
listen(fd, SOMAXCONN);
// int listen(int fd, int backlog(最大队列长度))
// 开始监听,出错返回-1
while (1) {
int new_fd = accept(fd, NULL, NULL);
// accept([fd], [监听地址], [监听地址结构体长度]) 第2,3个参数同bind()
// 接受连接请求,若无请求则阻塞(也有可能是EAGAIN,取决于你需要什么)
// 返回用于和对端通信的新的文件描述符,出错返回-1
// ... handle(new_fd);
close(new_fd); // 关闭文件描述符
}

Linux下建立GRE隧道并获取IPv6地址

虽然HE有提供免费的Tunnelbroker,不过那速度实在不怎么样。于是考虑在有IPv6地址托管主机上建立一个GRE Tunnel。
GRE Tunnel需要有内核模块ip_gre支持。远程主机有一段/64的IPv6,我将其中的一段/80分配给自己的机器。
使用iproute2工具。当然,你自己的机器需要有一个公网IPv4地址。

  1. 服务器的公网IPv4是$server_ipv4
  2. 自己电脑(或者路由器)的公网IPv4是$client_ipv4
  3. 服务器的IPv6段是a:b:c:d::/64
  4. 要分配下去的IPv6段是a:b:c:d:e::/80

服务器配置

脚本如下,需要root,建议用sudo -i

1
2
3
ip tunnel add gre-tunnel mode gre remote $client_ipv4 ttl 64
ip link set gre-tunnel up
ip addr add a:b:c:d:e::1/80 dev gre-tunnel
  • 第一行建立隧道,gre-tunnel是隧道名称,可以按自己喜欢的来,记得其他的也要一起改
  • 第二行激活隧道
  • 第三行分配IP地址