Linux kernel 笔记 (13)——“magic SysRq key”简介

Magic SysRq key”是一种组合键(例如在X86平台,是ALT-SysRq-<command key>),除非kernel被完全锁定(连中断都无法处理),否则就会响应这个组合键。这是一个很好的调试kernel方法。

要使用“magic SysRq key”功能,编译kernelCONFIG_MAGIC_SYSRQ要选择yes:“make menuconfig”->“Kernel hacking”->“Magic SysRq key”。当“magic SysRq key”功能编译进kernel后,可以通过/proc/sys/kernel/sysrq文件控制“magic SysRq key”键的功能。这个文件里的默认值是CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE这个配置项的值。例如:

0:关闭所有“`magic SysRq key`”功能
1:开启所有“`magic SysRq key`”功能
......

要注意/proc/sys/kernel/sysrq只能影响通过键盘使用“magic SysRq key”键的功能。而通过访问/proc/sysrq-trigger使用“magic SysRq key”键的功能则总是允许的。

常用的命令包括:

重启系统 (在系统hung住时特别管用):
echo b > /proc/sysrq-trigger

让系统crash:
echo c > /proc/sysrq-trigger

......

具体请参考kernel文档。

参考资料:
(1)magic sysrq: a linux system debugging technique
(2)sysrq.txt

Linux kernel 笔记 (12)——如何删除内核?

本文介绍如何删除自己编译安装的内核:

(1)在/boot文件夹下删除相关文件:

/boot/vmlinuz*KERNEL-VERSION*
/boot/initrd*KERNEL-VERSION*
/boot/System-map*KERNEL-VERSION*
/boot/config-*KERNEL-VERSION*(如果存在)

(2)删除和这个kernel相关的module文件夹。默认是在/lib/modules这个目录下:

/lib/modules/*KERNEL-VERSION*/

(3)修改grub启动文件:删除相应的menuentry,并记得修改default值。

参考资料:
How to: Linux delete or remove kernel

Linux kernel 笔记 (11)——pr_fmt

kernel代码中,很多.c文件开头都会有pr_fmt的定义,例如drivers\iommu\dmar.c

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt /* has to precede printk.h */

这个pr_fmt是针对这个特定的模块定义的。看一下include\linux\printk.h

#ifndef pr_fmt
#define pr_fmt(fmt) fmt
#endif

可以看到,如果这个模块没有定义自己的pr_fmt,那么就会使用默认的fmt。自己定义pr_fmt的好处是可以更清晰地打印这个模块的相关log,便于以后调试。

Linux kernel 笔记 (10) ——编译和安装Linux kernel命令简介

编译和安装Linux kernel时常用的命令:

make
编译出Linux kernel image文件,即vmlinuz

make modules
把在配置时选择M的配置项编译成一个一个的小模块(选项Y已经编译进vmlinuz,选项N会忽略掉)。这些模块会链接新编译出来的kernel image

make install
安装vmlinuz文件。如保存到/boot文件夹。

make modules_install
安装模块文件到/lib/modules/lib/modules/<version>

参考资料:
What happens in each step of the Linux kernel-building process?

Linux kernel 笔记 (9) ——如何理解“make oldconfig”?

在把一个老版本kernel.config文件拷贝到一个新版本的kernel源代码文件夹后,要执行“make oldconfig”命令。它的作用是检查已有的.config文件和Kconfig文件的规则是否一致,如果一致,就什么都不做,否则提示用户哪些源代码中有的选项在.config文件没有。

参考资料:
What does “make oldconfig” do exactly – Linux kernel makefile
How to understand ‘make oldconfig’?

Linux kernel 笔记 (8) ——vmlinux,vmlinuz,zImage,bzImage

vmlinux
一个非压缩的,静态链接的,可执行的,不能bootableLinux kernel文件。是用来生成vmlinuz的中间步骤。

vmlinuz
一个压缩的,能bootableLinux kernel文件。vmlinuzLinux kernel文件的历史名字,它实际上就是zImagebzImage

[root@Fedora boot]# file vmlinuz-4.0.4-301.fc22.x86_64
vmlinuz-4.0.4-301.fc22.x86_64: Linux kernel x86 boot executable bzImage, version 4.0.4-301.fc22.x86_64 (mockbuild@bkernel02.phx2.fedoraproject.o, RO-rootFS, swap_dev 0x5, Normal VGA

zImage
仅适用于640k内存的Linux kernel文件。

bzImage
Big zImage,适用于更大内存的Linux kernel文件。

总结一下,启动现代Linux系统时,实际运行的即为bzImage kernel文件。

参考资料:
vmlinuz Definition

Linux kernel 笔记 (7) ——内存地址类型

Linux中有以下几种内存地址类型:

(1)User virtual addresses
用户空间程序(user-space program)所看到和使用的地址。这些都是虚拟地址(virtual address),需要经过转化后,才能变成实际的内存物理地址。每个进程都拥有自己的虚拟地址空间。

(2)Physical addresses
实际的内存物理地址,也即是CPU用来访问物理内存的地址。

(3)Bus addresses
总线地址。外围设备所看到的物理内存地址。通常情况下,这个地址就是实际的内存物理地址。但是如果系统支持IOMMU的话,总线地址和实际的内存物理地址之间需要做一个映射。

(4)Kernel logic addresses
内核逻辑地址。内核逻辑地址会部分或全部地映射到物理内存地址,所以看起来就像实际的物理内存地址一样,并且这种映射是线性的,一对一的。在许多体系结构上,内核逻辑地址和物理内存地址只差一个常量偏移。内核虚拟地址的值通常存储在指针类型或unsigned long类型的变量里。kmalloc函数返回的就是内核逻辑地址。

(5)Kernel virtual addresses
内核虚拟地址。内核虚拟地址同内核逻辑地址相似的地方是都是把内核地址空间和实际物理内存的地址空间做映射,不同之处在于内核虚拟地址不要求这种映射是线性的,一对一的。内核逻辑地址都是内核虚拟地址,但是反过来则不成立。内核虚拟地址的值通常存储在指针类型变量里。vmallockmap返回的都是内核虚拟地址。

__pa(定义在<asm/page.h>)宏用来把内核逻辑地址映射为实际的内存物理地址;__va则用来把实际的内存物理地址映射为内核逻辑地址(仅限于low memory)。

Linux kernel 笔记 (6) ——__init和__initdata

__init__initdata定义在include/linux/init.h

/* These macros are used to mark some functions or 
 * initialized data (doesn't apply to uninitialized data)
 * as `initialization' functions. The kernel can take this
 * as hint that the function is used only during the initialization
 * phase and free up used memory resources after
 *
 * Usage:
 * For functions:
 * 
 * You should add __init immediately before the function name, like:
 *
 * static void __init initme(int x, int y)
 * {
 *    extern int z; z = x * y;
 * }
 *
 * If the function has a prototype somewhere, you can also add
 * __init between closing brace of the prototype and semicolon:
 *
 * extern int initialize_foobar_device(int, int, int) __init;
 *
 * For initialized data:
 * You should insert __initdata between the variable name and equal
 * sign followed by value, e.g.:
 *
 * static int init_variable __initdata = 0;
 * static const char linux_logo[] __initconst = { 0x32, 0x36, ... };
 *
 * Don't forget to initialize data not at file scope, i.e. within a function,
 * as gcc otherwise puts the data into the bss section and not into the init
 * section.
 * 
 * Also note, that this data cannot be "const".
 */

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init      __section(.init.text) __cold notrace
#define __initdata  __section(.init.data)
#define __initconst __constsection(.init.rodata)

__init修饰函数时表明函数只在kernel初始化阶段使用(函数放在.init.text区),在初始化完成后,这些函数所占用的内存就可以回收利用。举例如下:

static int __init parse_dmar_table(void)
{
    ....
}

__initdata作用类似于__init,只不过它修饰变量,并且存放在.init.data区。

Linux kernel 笔记 (5) ——spin_lock_irqsave

Linux kernel中最基本的lock原语就是spin lock(自旋锁)。实例代码如下:

static DEFINE_SPINLOCK(xxx_lock);

unsigned long flags;

spin_lock_irqsave(&xxx_lock, flags);
... critical section here ..
spin_unlock_irqrestore(&xxx_lock, flags);

以上代码总是安全的(包括可以用在中断处理函数),并可以在UPSMP上都可以正常工作。

Linux kernel 笔记 (4) ——memory barrier

由于编译器和处理器可以打乱程序的执行顺序,所以有些时候,我们需要“memory barrier”来保证内存访问指令的执行顺序(loadstore指令):

(1)rmb():提供一个读(load)操作的“memory barrier”,保证读操作不会跨越rmb()进行reorder。即rmb()之前的读(load)操作不会reorder rmb()之后,而rmb()之后的读(load)操作不会reorder rmb()之前。

(2)wmb():提供一个写(store)操作的“memory barrier”,同rmb类似,不过wmb()是保证写操作不会跨越wmb()进行reorder

(3)mb():保证读写操作都不会跨越mb()进行reorder

(4)read_barrier_depends()rmb()的一个变种,但仅保证有依赖关系的读操作不会跨越rmb()进行reorder

其它还有smp_rmb()smp_wmb()搜索。