《Linux内核设计与实现》读书笔记——从内核出发

内核下载

内核源码一般安装在/usr/src/linx目录下。

这里Ubuntu的例子中默认并没有安装,但是可以通过命令下载得到:

apt install linux-source

下载linux-2.6.34.1.tar.bz2,解压:

tar xvjf linux-2.6.34.1.tar.bz2

解压后得到:

内核源码树的根目录描述如下:

 

内核编译

编译内核之前首先需要配置。

配置项以CONFIG_XXX的形式存在。

配置项二选一:yes no

配置项三选一:yes no module

module表示以模块形式编译出来并独立存在,而不是包含在内核二进制中。

配置还可以是字符串或者整数

为了简化配置,有一些命令来进行处理:

make config

得到的结果如下:

需要手动一个个确定。

make menuconfig

得到的结果如下:

为了使用这个图形界面,需要安装ncurse:

sudo apt install libncurses5-dev

还有其它的图形界面可以使用:

make gconfig

这个暂时不关注。

还可以基于默认的配置为当前的体系结构创建一个配置:

make defconfig

得到的结果如下:

这里就创建了一个x86_64的默认配置。

最终生成了一个.config文件,里面包含了所有的配置,下面是.config中的一部分内容:

#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.34.1
# Tue Apr 21 00:24:46 2020
#
CONFIG_64BIT=y
# CONFIG_X86_32 is not set
CONFIG_X86_64=y
CONFIG_X86=y
CONFIG_OUTPUT_FORMAT="elf64-x86-64"
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig"
CONFIG_GENERIC_TIME=y
CONFIG_GENERIC_CMOS_UPDATE=y
CONFIG_CLOCKSOURCE_WATCHDOG=y
CONFIG_GENERIC_CLOCKEVENTS=y
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
CONFIG_LOCKDEP_SUPPORT=y
CONFIG_STACKTRACE_SUPPORT=y
CONFIG_HAVE_LATENCYTOP_SUPPORT=y
CONFIG_MMU=y
CONFIG_ZONE_DMA=y
CONFIG_NEED_DMA_MAP_STATE=y
CONFIG_GENERIC_ISA_DMA=y
CONFIG_GENERIC_IOMAP=y
CONFIG_GENERIC_BUG=y

你可以直接修改.config文件,修改完了之后运行如下命令来验证和更新配置。

make oldconfig

之后编译直接使用make就可。

 

内核安装

内核本身的安装需要根据平台来,比如x86,需要放到如下的目录:

然后更新GRUB,之后就可以使用这个内核。

module的安装也比较简单,它需要在root下执行安装:

make modules_install

关于Ubuntu内核的实际使用,可以参考

https://blog.csdn.net/weixin_38180645/article/details/82856407

因为可能导致系统异常的风险,这里不实际操作。

不过也可以使用虚拟机,这样风险比较低,可以参考

https://blog.csdn.net/jiangwei0512/article/details/52563639

这是一个使用QEMU的例子。

 

内核开发的特点

内核编译时既不能访问C库也不能访问标准的C头文件。

a. 大部分常用的C库函数都有内核中的实现。

b. 关于头文件:

i. <linux/inotify.h>对应代码位于include/linux/inotify.h

ii. <asm/ioctl.h>对应代码位于arch/<arch>/include/asm/ioctl.h

c. printk()相当于内核下的printf()。

 

内核编译时必须使用GNU C

a. 内核并不完全符合ANSI C标准,而是使用了gcc提供的许多C语言扩展部分。

i. 内联(inline)函数:

1) 函数形式:

static inline void func (int a)

2) 内联函数会在它所调用的位置上展开。

3) 内核开发者通常将时间要求比较高,而本身长度又比较短的函数定义为内联函数。

4) 内核中优先使用内联函数而不是宏。

ii. 内联汇编:

1) gcc编译器支持在C函数中嵌入汇编指令:

2) 诸如:

/*
 * Set up the IDT
 */
static void setup_idt(void)
{
    static const struct gdt_ptr null_idt = {0, 0};
    asm volatile("lidtl %0" : : "m" (null_idt));
}

3) 在偏近体系结构的底层或对执行时间要求严格的地方,一般使用汇编语言。

iii. 分支声明:

1) 类似如下:

if (unlikely(ret)) {
    pr_err("%s: CPU clock registration failed.\n", __func__);
    return ret;
}
/* no error */
if (likely(!dev->cmd_err))
    return msg->len;

2) likely和unlikely用来标识我们对分支的认识,如果我们知道某个分支本来就不太容易进就使用unlikely,反之如果很容易进就使用likely。

3) 当我们的判断是正确的时候,是可以提升性能的,否则性能会下降。

 

内核编程时缺乏像用户空间那样的内存保护机制

内核编程时难以执行浮点运算

a. 内核不能完美支持浮点操作。

b. 最好别用。

 

内核给每个进程只有很小的定长堆栈

a. 内核栈的大小跟体系架构有关。

b. x86上,栈的大小在编译时配置,可以是4KB也可以是8KB。

以32位为例(arch\x86\include\asm\page_32_types.h):

#ifdef CONFIG_4KSTACKS
#define THREAD_ORDER	0
#else
#define THREAD_ORDER	1
#endif
#define THREAD_SIZE 	(PAGE_SIZE << THREAD_ORDER)

如果在.config中没有配置则使用的是8K的堆栈。

而64位,默认就是8K(arch\x86\include\asm\page_64_types.h):

#define THREAD_ORDER	1
#define THREAD_SIZE  (PAGE_SIZE << THREAD_ORDER)

 

由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发

a. 常用的解决竞争的办法是自旋锁和信号量。

 

要考虑可移植的重要性

 

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/105962385