MartianZ!

《The Linux Kernel Primer》第1章习题

最近趁着双11购入不少之前一直买不起的电脑书,开始啃!

《The Linux Kernel Primer》这本书的内容给我感觉学习曲线会相对平缓一些,毕竟是Primer嘛(感觉很不理解书名为什么翻译成《Linux内核编程》),而且还有对应的课后习题。以后每一章看完做一下一部分习题,也算监督自己认真学习吧。答案仅供参考,如有错误,烦请指出,不胜感激。

第1章 习题:

1、 Unix系统和Unix的克隆系统之间有何区别?

Unix的克隆系统指实现了POSIX UNIX规范的操作系统,例如Linux。区别呢,实在是太多了,例如最明显的,两者的许可证不相同。

2、 术语“基于Power的Linux”指什么?

指运行在PowerPC架构的Linux操作系统。

3、 什么是用户空间?什么是内核空间?

在Linux种,操作系统划分为**内核空间**和**用户空间**(内核态和用户态)两部分。用户和程序员开发的应用程序位于用户空间,用户空间不能直接访问内核,通过内核定义的最外层例程——系统调用来访问。Linux操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,

具体内容涉及到CPU的保护模式和内存管理的知识。

4、 用户空间的程序访问内核功能的接口是什么?

在Linux中,系统调用是用户空间访问内核的主要机制。

5、 用户UID与用户名有何联系?

系统以唯一的用户名登录,对应一个UID(user identifier),内核通过UID验证用户的文件访问权限。

6、 列举文件与用户关联的方式。

文件总是与权限关联,权限保存在inode结构中,文件通过其拥有者的UID对应访问权,同时suid、sgid位可以设置执行进程以其拥有者UID(GID)身份运行,而不是以执行者的身份运行。

7、 列举Linux所支持的各种文件类型。

普通文件、目录文件、块设备文件、字符设备文件、链接文件、命名管道文件、套接字文件

8、 Shell是操作系统的一部分吗?

不是,但它是与操纵系统进行交互的主要用户接口。

9、 为什么既要有文件保护还要有文件模式?

 例如:root用户创建了一个上网认证程序netlogin,如果其他用户要上网也要用到这个程序,那就需要root用户运行chmod 755 netlogin命令使其他用户也能运行netlogin,但是netlogin执行时可能需要访问一些只有root用户才有权访问的文件,那么其他用户执行netlogin时可能因为权限不够还是不能上网,这种情况下,就可以用 chmod 4755 netlogin 设置其他用户在执行netlogin也有root用户的权限,从而顺利上网。(转载)

10、 列出在存放文件元数据的结构中都有哪类信息。

文件类型、大小、拥有者UID、权限等

11、 字符设备和块设备的根本区别是什么?

块设备的I/O以2的幂次方数据块大小进行传送,如软盘驱动器,字符设备如果不是伪设备,其I/O以字节流发送,如串口。

12、 是哪个组成部分让Linux内核成为多进程系统?

进程调度程序

13、 一个进程如何成为另一个进程的父进程?

进程通过fork()系统调用产生子进程,此时调用者即为父进程。

16、 分配进程的优先级有什么作用?所有用户能改变进程的优先级吗?为什么?

可以更好的处理进程调度,确保重要的任务获得更多的CPU时间。只有用户拥有对应权限才可以修改进程优先级。一般用户只能降低它们自己进程的优先级别,并限于 0 到 19 之间。超级用户(root)可以将任何进程的优先级设定为任何值。

17、设备驱动程序仅用于添加硬件支持吗?

不是,例如/dev/random字符设备的设备驱动程序,从其他设备收集熵信息。

U-Boot 启动过程初步分析

最近购入一块Cubieboard,使用Buildroot构建交叉编译链以后编译官方移植好的U-Boot,根据CPU Allwinner A10的资料,启动过程大概可分为5步:BootRom,SPL,Uboot,Kernel,RootFileSystem,先来研究RootRom到U-Boot的过程吧。

A10处理器在复位时从地址0x0开始执行指令,把BootRom映射到这一地址。A10将启动设备选择程序固化在CPU内部的一个32KB ROM中,根据Datasheet,默认的启动时序为SD Card0,NAND FLASH,SD Card2,SPI NOR FLASH。也可以通过一个电阻选择USB启动模式(有必要研究一下方便调试)。

为了不纠结于Nand分区的问题,直接用TF卡启动,根据官方Wiki,使用dd写入

dd if=spl/sunxi-spl.bin of=/dev/sdX bs=1024 seek=8
dd if=u-boot.img of=/dev/sdX bs=1024 seek=40

其中seek=40可在U-boot源代码u-boot-sunxi/include/configs/sunxi-common.h查看CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR定义得到

可以参考 https://github.com/linux-sunxi/u-boot-sunxi/commit/6a690123f9baa1965a76f8cdf4a34f5273caf773 这个Commit。(Increase SPL max size to 0x7600 and move u-boot.img start to 40KB offset)

直接找到ARM的start.S启动文件,找到the actual reset code。。

ARM的汇编代码真心看得比较纠结,可以看出屏蔽了FIQ和IRQ Interrupts,设置了vector,和一些更底层的初始化,具体可以留到以后再研究。然后就bl _main了。

_main在crt0.S里,这个commit:https://github.com/linux-sunxi/u-boot-sunxi/commit/e05e5de7fae5bec79617e113916dac6631251156 把C runtime setup code从start.S移到一个单独的crt0.S,作为一个ARM架构的单独的lib,这样方便各个不同的ARM芯片调用。

进去以后看一下一部分汇编:

//为 board_init_f 调用准备GD和堆栈的RAM空间
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic sp, sp, #7  /* 8-byte alignment for ABI compliance 8字节对齐*/
/*SP这里是一个64位的向下生长的stack,清空后3位让它变成8的倍数*/
sub sp, #GD_SIZE    /* allocate one GD above SP */
bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
mov r8, sp      /* GD is above SP */
mov r0, #0
bl  board_init_f //传递参数 r0 = 0

其中全局变量gd的定义在arch/arm/include/asm/global_data.h:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

直接使用了ARM的r8寄存器,ARM的寄存器相比PPC多很多,像PPC的一些扁平设备树的动态接口设计可能就不再需要了吧。

board_init_f定义在board.c,主要服务就是硬件的初始化init_sequence数组,设置gd的信息。

接着crt0.S里面清除BSS段,进一步完善环境,执行board_init_r进入第二阶段的初始化。

这个过程涉及好多知识,在阅读汇编代码的过程中各种翻ARM汇编和ARM寄存器的手册,看得还是不太懂,也涉及到C语言的底层问题例如函数调用与堆栈的变化。先看到这,有时间再硬啃这三个(start.S、crt0.S、relocate.S)汇编文件。

电子科技大学(清水河)寝室网络OpenWRT IPv4+IPv6

在电子科大清水河校区,寝室用的是电信网……配置起来真心略蛋疼……

记录一下整体的配置过程和思路,防止哪次Router悲剧……

第一步 改ROM、RAM,编译 OpenWRT(记得勾选各种IPv6支持),刷路由器

这里就不多说了……我用的路由器是TP-Link WR843N,热风枪改RAM到16MiB内存,跑OpenWRT就毫无压力了。

第二步 IPv4 NAT

校园电信网络采用的是WEP 802.1x EAP+PEAP+MSCHAPV2,相比使用锐捷什么的或者修改后的认证算法之类的学校来说还是很厚道了,在Linux下一般使用wpa supplicant进行认证。

OpenWRT自带的是wpad-mini,无法使用802.1x认证……于是首先要卸载wpad-mini,然后安装wpad,wpad就是wpa_supplicant和hostapd的集合。这里具体过程也不再记录了,scp传输文件以后opkg即可。

设置认证的配置文件,我这里把配置文件放在 /etc/802.1x.conf

ctrl_interface=/var/run/wpa_supplicant
ap_scan=0
network={
    key_mgmt=IEEE8021X
    eap=MD5
    identity="用户名"
    password="密码"
    eapol_flags=0
}

接着运行下面的命令

killall wpa_supplicant

wpa_supplicant -B -c /etc/802.1x.conf -ieth0 -Dwired

推荐把上面的命令设置成开机自动运行,这样每次路由器重启就完全自动连接了。如果没有错误的话就认证结束了,这时候到LuCI里面,网络 - 接口,找到WAN,配置为DHCP客户端,然后点击右边的 连接,如果没有问题的话就可以获得IPv4和IPv6地址了。如下图:

img1

这个时候应该可以连接到路由器上网了,IPv4的NAT应该都自动配置好了,其他设置按需进行吧。

第三步 IPv6 NDP

IPv4由于NAT的存在共享上网是想当的简单(随便一个不懂电脑的萌妹子给家里搞一台TP-Link,就知道设置PPPoE认证然后就NAT组建家庭局域网共享上网了……让我等苦逼技术死宅没有了上门帮忙的机会 QAQ),IPv6标准协议里面木有NAT,让我顿时费解了一把,后来谷歌了一下发现北邮有一群学生做了一个创新项目实现了IPv6的NAT,当时感觉是各种膜拜啊……还是内核级的项目啊卧槽……难道我又要改内核代码然后重新编译OpenWRT了啊……

后来仔细研究了一下,果然北邮那个项目还是没啥意思的,有点坑经费的感觉,因为IPv6有一种更好的解决方案:Proxy Neighbour Discovery Protocol(邻居发现协议),具体可以看:http://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol

简单来说,NAT就是把内网终端伪装起来请求出去,同时数据会到有外网地址的Router,再让此Router把数据包转发给客户端机器。由于IPv6的地址空间是相当相当的大,没有必要再公用一个外网地址,可以让每个内网的终端都具有一个外网地址

这个地址依然是不可再路由,这时就需要让具有外网地址的Router帮忙告诉它的上层Router,这些外网地址在这个Router的内网中。

这种方式牛逼之处在于……什么UPnP,什么NAT穿透,全部都不需要了……P2P什么的嘛……完全无压力……

继续配置,我这里外网网卡是eth0,内网是br-lan

先安装ndppd

opkg update && opkg install ndppd

看一下我的Global IPv6地址:

root@OpenWrt:/etc# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 8C:21:0A:A6:94:B3  
      inet addr:121.48.171.138  Bcast:121.48.171.255  Mask:255.255.255.128
      inet6 addr: 2001:250:2000:7520:8e21:aff:fea6:94b3/64 Scope:Global
      inet6 addr: fe80::8e21:aff:fea6:94b3/64 Scope:Link
      UP BROADCAST RUNNING ALLMULTI MULTICAST  MTU:1500  Metric:1
      RX packets:517397 errors:0 dropped:450 overruns:0 frame:0
      TX packets:777032 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000 
      RX bytes:177017984 (168.8 MiB)  TX bytes:688621714 (656.7 MiB)
      Interrupt:4

是2001:250:2000:7520:8e21:aff:fea6:94b3/64,因为是/64,所以无法继续划分子网,就要使用刚才说的邻居发现协议。

然后给内网网卡br-lan设置与eth0的地址前64位相同,后64位不同的IPv6地址(不要直接抄袭我的,如果你也在电子科大清水河……这样咱们会冲突的),设置时前缀长度要大于64:

ip -6 addr add 2001:250:2000:7520:1::1/80 dev br-lan

修改/etc/ndppd.conf

proxy eth0{
    router yes
    timeout 500
    ttl 30000


    rule 2001:250:2000:7520:1::/80 {
      auto
    }
}

然后运行ndppd:/etc/init.d/ndppd start,这样就配置好了。比内核级的NAT实现要轻松许多。

但是这个时候还不能客户端自动获得IP,radvd配置只能前缀为64,所以还需要dhcpv6 server:

opkg install radvd
opkg install wide-dhcpv6-server

配置/etc/config/radvd:

config interface
    option interface        'lan'
    option AdvSendAdvert    1
    option AdvManagedFlag   1
    option AdvOtherConfigFlag 1
    list client             ''

config prefix
    option interface        'lan'
    # If not specified, a non-link-local prefix of the interface is used
    list prefix             ''
    option AdvOnLink        1
    option AdvAutonomous    1
    option AdvRouterAddr    0

配置/etc/config/dhcp6s,enabled设置为1

配置/etc/dhcp6s.conf

interface br-lan {
    address-pool pool1 86400;
};
pool pool1 {
    range 2001:250:2000:7520:1::200 to 2001:250:2000:7520:1::300 ;
};

启动radvd和dhcpv6 server:

/etc/init.d/radvd start
/etc/init.d/dhcp6s start

注意顺序,如果遇到错误,可以:

/etc/init.d/radvd restart
/etc/init.d/ndppd restart

这样我们就配置好了IPv6的邻居发现协议和IP地址的分配,这个时候连上路由器的客户端已经可以自动获得IPv4和IPv6的地址并无障碍访问IPv4和IPv6的网络了:

img2

本地Ping Google IPv6:

MartiandeMacBook-Pro:~ MartianZ$ ping6 ipv6.google.com
PING6(56=40+8+8 bytes) 2001:250:2000:7520:1::100 --> 2404:6800:4008:c01::68
16 bytes from 2404:6800:4008:c01::68, icmp_seq=0 hlim=46 time=110.295 ms
16 bytes from 2404:6800:4008:c01::68, icmp_seq=1 hlim=46 time=113.267 ms
16 bytes from 2404:6800:4008:c01::68, icmp_seq=3 hlim=46 time=109.890 ms
^C
--- ipv6.l.google.com ping6 statistics ---
4 packets transmitted, 3 packets received, 25.0% packet loss
round-trip min/avg/max/std-dev = 109.890/111.151/113.267/1.506 ms

外部Ping本地:

IPv6 Ping Output:
PING 2001:250:2000:7520:1::100(2001:250:2000:7520:1::100) 32 data bytes
40 bytes from 2001:250:2000:7520:1::100: icmp_seq=0 ttl=41 time=373 ms
40 bytes from 2001:250:2000:7520:1::100: icmp_seq=1 ttl=42 time=374 ms
40 bytes from 2001:250:2000:7520:1::100: icmp_seq=2 ttl=42 time=373 ms
40 bytes from 2001:250:2000:7520:1::100: icmp_seq=3 ttl=42 time=372 ms

--- 2001:250:2000:7520:1::100 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3011ms
rtt min/avg/max/mdev = 372.783/373.540/374.239/0.540 ms, pipe 2

在外面的网络测试了一下,我的IP地址[2001:250:2000:7520:1::100],直接可以ping通,并且 http://[2001:250:2000:7520:1::100]:8080也可以直接访问我电脑的Web Server,类似一个独立的IP一样。无需再考虑NAT穿透的事情。

全部配置完成~撒花~