kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

Linux 内核引导参数简介

概述

内核引导参数大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导参数多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导参数。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导参数,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导参数说明。大多数参数是通过"__setup(… , …)“函数设置的,少部分是通过"early_param(… , …)"函数设置的,逗号前的部分就是引导参数的名称,后面的部分就是处理这些参数的函数名。

[提示]你可以在源码树的根目录下试一试下面两个命令:

1
2
grep -r '\b__setup *(' *
grep -r '\bearly_param *(' *

格式上,多个参数之间用空格分割,参数值是一个逗号分割的列表,并且参数值中不能包含空白。

正确:ether=9,0x300,0xd0000,0xd4000,eth0 root=/dev/sda2
错误:ether = 9, 0x300, 0xd0000, 0xd4000, eth0 root = /dev/sda2

注意,所有引导参数都是大小写敏感的!

在内核运行起来之后,可以通过 cat /proc/cmdline 命令查看当初使用的引导参数以及相应的值。

内核模块

对于模块而言,引导参数只能用于直接编译到核心中的模块,格式是"模块名.参数=值",比如"usbcore.blinkenlights=1"。动态加载的模块则可以在 modprobe 命令行上指定相应的参数值,比如"modprobe usbcore blinkenlights=1"。

可以使用"modinfo -p ${modulename}“命令显示可加载模块的所有可用参数。已经加载到内核中的模块会在 /sys/module/${modulename}/parameters/ 中显示出其参数,并且某些参数的值还可以在运行时通过"echo -n ${value} > /sys/module/${modulename}/parameters/${parm}"进行修改。

内核如何处理引导参数

绝大部分的内核引导参数的格式如下(每个参数的值列表中最多只能有十项):

name[=value_1][,value_2]…[,value_10]

如果"name"不能被识别并且满足"name=value"的格式,那么将被解译为一个环境变量(比如"TERM=linux"或"BOOT_IMAGE=vmlinuz.bak"),否则将被原封不动的传递给 init 程序(比如"single")。

内核可以接受的参数个数没有限制,但是整个命令行的总长度(参数/值/空格全部包含在内)却是有限制的,定义在 include/asm/setup.h 中的 COMMAND_LINE_SIZE 宏中(对于X86_64而言是2048)。

内核引导参数精选

由于引导参数多如牛毛,本文不可能涉及全部,因此本文只基于 X86_64 平台以及 Linux-3.13.2 精选了一些与设备无关的引导参数以及少部分与设备有关的引导参数,过时的参数、非x86平台参数、与设备有关的参数,基本上都被忽略了。

[提示]内核源码树下的 Documentation/kernel-parameters.txt 和 Documentation/x86/x86_64/boot-options.txt 文件列出了所有可用的引导参数,并作了简要说明。

标记说明

并不是所有的参数都是永远可用的,只有在特定的模块存在并且相应的硬件也存在的情况下才可用。引导参数上面的方括号说明了其依赖关系,其中使用的标记解释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
ACPI     开启了高级配置与电源接口(CONFIG_ACPI)支持
AGP      开启了AGP(CONFIG_AGP)支持
APIC     开启了高级可编程中断控制器支持(2000年以后的CPU都支持)
APPARMOR 开启了AppArmor(CONFIG_SECURITY_APPARMOR)支持
DRM      开启了Direct Rendering Manager(CONFIG_DRM)支持
EFI      开启了EFI分区(CONFIG_EFI_PARTITION)支持
EVM      开启了Extended Verification Module(CONFIG_EVM)支持
FB       开启了帧缓冲设备(CONFIG_FB)支持
HIBERNATION  开启了"休眠到硬盘"(CONFIG_HIBERNATION)支持
HPET_MMAP    允许对HPET寄存器进行映射(CONFIG_HPET_MMAP)
HW       存在相应的硬件设备
IOMMU    开启了IOMMU(CONFIG_IOMMU_SUPPORT)支持
IOSCHED  开启了多个不同的I/O调度程序(CONFIG_IOSCHED_*)
IPV6     开启了IPv6(CONFIG_IPV6)支持
IP_PNP   开启了自动获取IP的协议(DHCP,BOOTP,RARP)支持
IP_VS_FTP    开启了IPVS FTP协议连接追踪(CONFIG_IP_VS_FTP)支持
KVM      开启了KVM(CONFIG_KVM_*)支持
LIBATA   开启了libata(CONFIG_ATA)驱动支持
LOOP     开启了回环设备(CONFIG_BLK_DEV_LOOP)支持
MCE      开启了Machine Check Exception(CONFIG_X86_MCE)支持
MOUSE    开启了鼠标(CONFIG_INPUT_MOUSEDEV)支持
MSI      开启了PCI MSI(CONFIG_PCI_MSI)支持
NET      开启了网络支持
NETFILTER    开启了Netfilter(CONFIG_NETFILTER)支持
NFS      开启了NFS(网络文件系统)支持
NUMA     开启了NUMA(CONFIG_NUMA)支持
PCI      开启了PCI总线(CONFIG_PCI)支持
PCIE     开启了PCI-Express(CONFIG_PCIEPORTBUS)支持
PNP      开启了即插即用(CONFIG_PNP)支持
PV_OPS   内核本身是半虚拟化的(paravirtualized)
RAID     开去了软RAID(CONFIG_BLK_DEV_MD)支持
SECURITY 开启了多个不同的安全模型(CONFIG_SECURITY)
SELINUX  开启了SELinux(CONFIG_SECURITY_SELINUX)支持
SLUB     开启了SLUB内存分配管理器(CONFIG_SLUB)
SMP      开启了对称多处理器(CONFIG_SMP)支持
TPM      开启了可信赖平台模块(CONFIG_TCG_TPM)支持
UMS      开启了USB大容量存储设备(CONFIG_USB_STORAGE)支持
USB      开启了USB(CONFIG_USB_SUPPORT)支持
USBHID   开启了USB HID(CONFIG_USB_HID)支持
VMMIO    开启了使用内存映射机制的virtio设备驱动(CONFIG_VIRTIO_MMIO)
VT       开启了虚拟终端(CONFIG_VT)支持

此外,下面的标记在含义上与上面的有所不同:

1
2
3
BUGS    用于解决某些特定硬件的缺陷
KNL     是一个内核启动参数
BOOT    是一个引导程序参数

标记为"BOOT"的参数实际上由引导程序(例如GRUB)使用,对内核本身没有直接的意义。如果没有特别的需求,请不要修改此类参数的语法,更多信息请阅读 Documentation/x86/boot.txt 文档。

说明:下文中的 [KMG] 后缀表示 210, 220, 230 的含义。

控制台与终端

1
2
3
4
5
6
7
8
9
10
11
12
[KNL]
console=设备及选项
	设置输出控制台使用的设备及选项。例如:ttyN 表示使用第N个虚拟控制台。其它用法主要针对嵌入式环境(Documentation/serial-console.txt)。
[KNL]
consoleblank=秒数
	控制台多长时间无操作后黑屏,默认值是600秒,设为0表示禁止黑屏。
[HW]
no_console_suspend
	永远也不要将控制台进入休眠状态。因为当控制台进入休眠之后,所有内核的消息就都看不见了(包括串口与VGA)。开启此参数有助于调试系统在休眠/唤醒中发生的故障。
[VT]
vt.default_utf8={0|1}
	是否将所有TTY都默认设置为UTF-8模式。默认值"1"表示将所有新打开的终端都设置为UTF-8模式。

日志与调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
earlyprintk=设备[,keep]
	使用哪个设备显示早期的引导信息,主要用于调试硬件故障。此选项默认并未开启,因为在某些情况下并不能正常工作。
	在传统的控制台初始化之前,在哪个设备上显示内核日志信息。不使用此参数,那么你将永远没机会看见这些信息。
	在尾部加上",keep"选项表示在真正的内核控制台初始化并接管系统后,不会抹掉本选项消息的显示。
	earlyprintk=vga 表示在VGA上显示内核日志信息,这是最常用的选项,但不能用于EFI环境。
	earlyprintk=efi v3.13新增,表示将错误日志写入EFI framebuffer,专用于EFI环境。
	earlyprintk=xen 仅可用于XEN的半虚拟化客户机。
loglevel={0|1|2|3|4|5|6|7}
	设置内核日志的级别,所有小于该数字的内核信息(具有更高优先级的信息)都将在控制台上显示出来。这个级别可以使用 klogd 程序或者修改 /proc/sys/kernel/printk 文件进行调整。取值范围是"0"(不显示任何信息)到"7"(显示所有级别的信息)。建议至少设为"4"(WARNING)。[提示]级别"7"要求编译时加入了调试支持。
[KNL]
ignore_loglevel
	忽略内核日志等级的设置,向控制台输出所有内核消息。仅用于调试目的。
[KNL]
debug
	将引导过程中的所有调试信息都显示在控制台上。相当于设置"loglevel=7"(DEBUG)。
[KNL]
quiet
	静默模式。相当于设置"loglevel=4"(WARNING)。
log_buf_len=n[KMG]
	内核日志缓冲区的大小。"n"必须是2的整数倍(否则会被自动上调到最接近的2的整数倍)。该值也可以通过内核配置选项CONFIG_LOG_BUF_SHIFT来设置。
[KNL]
initcall_debug
	跟踪所有内核初始化过程中调用的函数。有助于诊断内核在启动过程中死在了那个函数上面。
kstack=N
	在内核异常(oops)时,应该打印出内核栈中多少个字(word)到异常转储中。仅供调试使用。
[KNL]
kmemleak={on|off}
	是否开启检测内核内存泄漏的功能(CONFIG_DEBUG_KMEMLEAK),默认为"on",仅供调试使用。
	检测方法类似于跟踪内存收集器,一个内核线程每10分钟(默认值)扫描内存,并打印发现新的未引用的对象的数量。
[KNL]
memtest=整数
	设置内存测试(CONFIG_MEMTEST)的轮数。"0"表示禁止测试。仅在你确实知道这是什么东西并且确实需要的时候再开启。
norandmaps
	默认情况下,内核会随机化程序的启动地址,也就是每一次分配给程序的虚拟地址空间都不一样,主要目的是为了防止缓冲区溢出攻击。但是这也给程序调试增加了麻烦,此参数(相当于"echo 0 > /proc/sys/kernel/randomize_va_space")的目的就是禁用该功能以方便调试。
[PNP]
pnp.debug=1
	开启PNP调试信息(需要内核已开启CONFIG_PNP_DEBUG_MESSAGES选项),仅用于调试目的。也可在运行时通过 /sys/module/pnp/parameters/debug 来控制。
show_msr=CPU数
	显示启动时由BIOS初始化的MSR(Model-Specific Register)寄存器设置。CPU数设为"1"表示仅显示"boot CPU"的设置。
printk.time={0|1}
	是否在每一行printk输出前都加上时间戳,仅供调试使用。默认值是"0"(不添加)
boot_delay=毫秒数
	在启动过程中,为每一个printk动作延迟指定的毫秒数,取值范围是[0-10000](最大10秒),超出这个范围将等价于"0"(无延迟)。仅用于调试目的。
pause_on_oops=秒数
	当内核发生异常时,挂起所有CPU的时间。当异常信息太多,屏幕持续滚动时,这个选项就很有用处了。主要用于调试目的。

异常检测与处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[MCE]
mce=off
	彻底禁用MCE(CONFIG_X86_MCE)
[MCE]
mce=dont_log_ce
	不为已纠正错误(corrected error)记录日志。
[MCE]
mce=容错级别[,超时]
	容错级别(还可通过sysfs设置):
	0 在出现未能纠正的错误时panic,记录所有已纠正的错误
	1(默认值) 在出现未能纠正的错误时panic或SIGBUS,记录所有已纠正的错误
	2 在出现未能纠正的错误时SIGBUS或记录日志,记录所有已纠正的错误
	3 从不panic或SIGBUS,记录所有日志。仅用于调试目的。
	超时(单位是微秒[百万分之一秒]):在machine check时等待其它CPU的时长,"0"表示不等待。
[ACPI]
erst_disable
	禁用ERST(Error Record Serialization Table)支持。主要用于解决某些有缺陷的BIOS导致的ERST故障。
[ACPI]
hest_disable
	禁用HEST(Hardware Error Source Table)支持。主要用于解决某些有缺陷的BIOS导致的HEST故障。
[KNL]
nosoftlockup
	禁止内核进行软死锁检测
[KNL]
softlockup_panic={0|1}
	是否在检测到软死锁(soft-lockup)的时候让内核panic,其默认值由 CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE 确定
[KNL]
nowatchdog
	禁止硬死锁检测(NMI watchdog)
[KNL,BUGS]
nmi_watchdog={0|panic|nopanic}
	配置nmi_watchdog(不可屏蔽中断看门狗)。更多信息可查看"lockup-watchdogs.txt"文档。
	0 表示关闭看门狗;
	panic 表示出现看门狗超时(长时间没喂狗)的时候触发内核错误,通常和"panic="配合使用,以实现在系统出现锁死的时候自动重启。
	nopanic 正好相反,表示即使出现看门狗超时(长时间没喂狗),也不触发内核错误。
unknown_nmi_panic
	在收到未知的NMI(不可屏蔽中断)时直接panic
oops=panic
	在内核oops时直接panic(而默认是仅仅杀死oops进程[这样做会有很小的概率导致死锁]),而且这同样也会导致在发生MCE(CONFIG_X86_MCE)时直接panic。主要目的是和"panic="参数连用以实现自动重启。
[KNL]
panic=秒数
	内核在遇到panic时等待重启的行为:
	秒数>0 等待指定的秒数后重启
	秒数=0(默认值) 只是简单的挂起,而永不重启
	秒数<0 立即重启

时钟(Timer)

时钟(Timer)的功能有两个:(1)定时触发中断;(2)维护和读取当前时间。

x86_64平台常见的时钟硬件有以下这些:
RTC(Real Time Clock) 实时时钟的独特之处在于,RTC是主板上一块电池供电的CMOS芯片(精度一般只到秒级),RTC(Clock)吐出来的是"时刻"(例如"2014-2-22 23:38:44"),而其他硬件时钟(Timer)吐出来的是"时长"(我走过了XX个周期,按照我的频率,应该是10秒钟)。
PIT(Programmable Interval Timer) PIT是最古老的时钟源,产生周期性的时钟中断(IRQ0),精度在100-1000Hz,现在基本已经被HPET取代。
APIC Timer 这是PIT针对多CPU环境的升级,每个CPU上都有一个APIC Timer(而PIT则是所有CPU共享的),但是它经常有BUG且精度也不高(3MHz左右),所实际很少使用。
ACPI Timer(Power Management Timer) 它唯一的功能就是为每个时钟周期提供一个时间戳,用于提供与处理器速度无关的可靠时间戳。但其精度并不高(3.579545MHz)。
HPET(High Precision Event Timer) HPET提供了更高的精度(14.31818MHz)以及更宽的计数器(64位)。HPET可以替代前述除RTC之外的所有时钟硬件(Timer),因为它既能定时触发中断,又能维护和读取当前时间。一个HPET包含了一个固定频率的数值递增的计数器以及3-32个独立计数器,每个计数器又包含了一个比较器和一个寄存器,当两者数值相等时就会触发中断。HPET的出现将允许删除芯片组中的一些冗余的旧式硬件。2006年之后的主板基本都已支持HPET。
TSC(Time Stamp Counter) TSC是位于CPU里面的一个64位寄存器,与传统的周期性时钟不同,TSC并不触发中断,它是以计数器形式存在的单步递增性时钟。也就是说,周期性时钟是通过周期性触发中断达到计时目的,如心跳一般。而单步递增时钟则不发送中断,取而代之的是由软件自己在需要的时候去主动读取TSC寄存器的值来获得时间。TSC的精度(纳秒级)远超HPET并且速度更快,但仅能在较新的CPU(Sandy Bridge之后)上使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[HW,ACPI]
acpi_skip_timer_override
	用于解决某些有缺陷的Nvidia nForce2 BIOS中的计时器覆盖问题(例如开启ACPI后频繁死机或时钟故障)。
[HW,ACPI]
acpi_use_timer_override
	用于解决某些有缺陷的Nvidia nForce5 BIOS中的计时器覆盖问题(例如开启ACPI后频繁死机或时钟故障)。
[APIC]
no_timer_check
	禁止运行内核中时钟IRQ源缺陷检测代码。主要用于解决某些AMD平台的CPU占用过高以及时钟过快的故障。
pmtmr=十六进制端口号
	手动指定pmtimer(CONFIG_X86_PM_TIMER)的I/O端口(16进制值),例如:pmtmr=0x508
acpi_pm_good
	跳过pmtimer(CONFIG_X86_PM_TIMER)的bug检测,强制内核认为这台机器的pmtimer没有毛病。用于解决某些有缺陷的BIOS导致的故障。
[APIC]
apicpmtimer
	使用pmtimer(CONFIG_X86_PM_TIMER)来校准APIC timer。此参数隐含了"apicmaintimer"。用于PIT timer彻底坏掉的场合。
[APIC]
apicmaintimer
noapicmaintimer
	apicmaintimer 将APIC timer用于计时(而不是PIT/HPET中断)。这主要用于PIT/HPET中断不可靠的场合。
	noapicmaintimer 不将APIC timer用于计时(而是使用PIT/HPET中断)。这是默认值。但有时候依然需要明确指定。
[APIC]
lapic_timer_c2_ok
	按照ACPI规范的要求,local APIC Timer 不能在C2休眠状态下关闭,但可以在C3休眠状态下关闭。但某些BIOS(主要是AMD平台)会在向操作系统报告CPU进入C2休眠状态时,实际进入C3休眠状态。因此,内核默认采取了保守的假定:认为 local APIC Timer 在C2/C3状态时皆处于关闭状态。如果你确定你的BIOS没有这个问题,那么可以使用此参数明确告诉内核,即使CPU在C2休眠状态,local APIC Timer 也依然可用。
[APIC]
noapictimer
	禁用CPU Local APIC Timer
enable_timer_pin_1
disable_timer_pin_1
	开启/关闭APIC定时器的PIN1,内核将尽可能自动探测正确的值。但有时需要手动指定以解决某些有缺陷的ATI芯片组故障。
clocksource={jiffies|acpi_pm|hpet|tsc}
	强制使用指定的时钟源,以代替内核默认的时钟源。
	jiffies 最差的时钟源,只能作为最后的选择。
	acpi_pm [ACPI]符合ACPI规范的主板都提供的硬件时钟源(CONFIG_X86_PM_TIMER),提供3.579545MHz固定频率,这是传统的硬件时钟发生器。
	hpet 一种取代传统"acpi_pm"的高精度硬件时钟源(CONFIG_HPET),提供14.31818MHz固定频率。2007年以后的芯片组一般都支持。
	tsc TSC(Time Stamp Counter)的主体是位于CPU里面的一个64位TSC寄存器,与传统的以中断形式存在的周期性时钟不同,TSC是以计数器形式存在的单步递增性时钟,两者的区别在于,周期性时钟是通过周期性触发中断达到计时目的,如心跳一般。而单步递增时钟则不发送中断,取而代之的是由软件自己在需要的时候去主动读取TSC寄存器的值来获得时间。TSC的精度更高并且速度更快,但仅能在较新的CPU(Sandy Bridge之后)上使用。
[KNL]
highres={"on"|"off"}
	启用(默认值)还是禁用高精度定时器模式。主要用于关闭主板上有故障的高精度时钟源。
nohpet
	禁用HPET timer(CONFIG_HPET)
[HPET_MMAP]
hpet_mmap
	v3.13新增,默认允许对HPET寄存器进行映射,相当于开启了内核CONFIG_HPET_MMAP_DEFAULT选项。需要注意的是,某些包含HPET硬件寄存器的页中同时还含有其他不该暴露给用户的信息。
notsc
tsc=reliable
tsc=noirqtime
	设置TSC时钟源的属性。
	notsc 表示不将TSC用作"wall time"时钟源,主要用于不能在多个CPU之间保持正确同步的SMP系统。
	tsc=reliable 表示TSC时钟源是绝对稳定的,关闭启动时和运行时的稳定性检查。用于在某些老旧硬件/虚拟化环境使用TSC时钟源。
	tsc=noirqtime 不将TSC用于统计进程IRQ时间。主要用于在RDTSC速度较慢的CPU上禁止内核的CONFIG_IRQ_TIME_ACCOUNTING功能。
	关于"TSC时钟源",详见"clocksource="参数的说明。

中断

常见的中断控制器有两种:传统的8259A和新式的APIC,前者也被称为"PIC"。8259A只适合单CPU的场合,而APIC则能够把中断传递给系统中的每个CPU,从而充分挖掘SMP体系结构的并行性。所以8259A已经被淘汰了。APIC系统由3部分组成:APIC总线(前端总线)、IO-APIC(南桥)、本地APIC(CPU)。每个CPU中集成了一个本地APIC,负责传递中断信号到处理器。而IO-APIC是系统芯片组中一部分,负责收集来自I/O设备的中断信号并发送到本地APIC。APIC总线则是连接IO-APIC和各个本地APIC的桥梁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[SMP,APIC]
noapic
	禁止使用IO-APIC(输入输出高级可编程输入控制器),主要用于解决某些有缺陷的BIOS导致的APIC故障。
[APIC]
nolapic
disableapic
	禁止使用local APIC。主要用于解决某些有缺陷的BIOS导致的APIC故障。"nolapic"是为了保持传统习惯的兼容写法,与"disableapic"的含义相同。
[APIC]
nox2apic
	关闭x2APIC支持(CONFIG_X86_X2APIC)
[APIC]
x2apic_phys
	在支持x2apic的平台上使用physical模式代替默认的cluster模式。
[KNL]
threadirqs
	强制线程化所有的中断处理器(明确标记为IRQF_NO_THREAD的除外)
[SMP,APIC]
pirq=
	手动指定mp-table的设置。此参数仅对某些有缺陷的、具备多个IO-APIC的高端主板有意义。详见Documentation/x86/i386/IO-APIC.txt文档
[HW]
irqfixup
	用于修复简单的中断问题:当一个中断没有被处理时搜索所有可用的中断处理器。用于解决某些简单的固件缺陷。
[HW]
irqpoll
	用于修复高级的中断问题:当一个中断没有被处理时搜索所有可用的中断处理器,并且对每个时钟中断都进行搜索。用于解决某些严重的固件缺陷。

ACPI

高级配置与电源管理接口(Advanced Configuration and Power Interface)是提供操作系统与应用程序管理所有电源管理接口,包括了各种软件和硬件方面的规范。2004年推出3.0规范;2009年推出4.0规范;2011年推出5.0规范。2013年之后新的ACPI规格将由UEFI论坛制定。ACPI可以实现的功能包括:电源管理;性能管理;配置与即插即用;系统事件;温度管理;电池管理;SMBus控制器;嵌入式控制器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[HW,ACPI]
acpi={force|off|noirq|strict|rsdt|nocmcff|copy_dsdt}
	ACPI的总开关。
	force 表示强制启用ACPI(即使BIOS中已关闭);
	off 表示强制禁用ACPI(即使BIOS中已开启);
	noirq 表示不要将ACPI用于IRQ路由;
	strict 表示严格要求系统遵循ACPI规格(降低兼容性);
	rsdt 表示使用老旧的RSDT(Root System Description Table)代替较新的XSDT(Extended System Description Table);
	copy_dsdt 表示将DSDT(Differentiated System Description Table)复制到内存中。
	更多信息可参考Documentation/power/runtime_pm.txt以及"pci=noacpi"。
[HW,ACPI]
acpi_backlight={vendor|video}
	选择屏幕背光亮度调节驱动。
	video(默认值)表示使用通用的ACPI video.ko驱动(CONFIG_ACPI_VIDEO),该驱动仅可用于集成显卡。
	vendor表示使用厂商特定的ACPI驱动(thinkpad_acpi,sony_acpi等)。
	详见Documentation/acpi/video_extension.txt文档。
[HW,ACPI]
acpi_os_name="字符串"
	告诉ACPI BIOS操作系统的名称。
	常用于哄骗有缺陷的BIOS,让其以为运行的是Windows系统而不是Linux系统。
	"Linux" = Linux
	"Microsoft Windows" = Windows 98
	"Windows 2000" = Windows 2000
	"Windows 2001" = Windows XP
	"Windows 2001 SP2" = Windows XP SP2
	"Windows 2001.1" = Windows Server 2003
	"Windows 2001.1 SP1" = Windows Server 2003 SP1
	"Windows 2006" = Windows Vista
	"Windows 2006 SP1" = Windows Vista SP1
	"Windows 2006.1" = Windows Server 2008
	"Windows 2009" = Windows 7 / Windows Server 2008 R2
	"Windows 2012" = Windows 8 / Windows Server 2012
	"Windows 2013" = Windows 8.1 / Windows Server 2012 R2
[HW,ACPI]
acpi_osi="字符串"
	对于较新的内核(Linux-2.6.23之后)而言,当BIOS询问内核:"你是Linux吗?",内核都会回答"No",但历史上(Linux-2.6.22及更早版本)内核会如实回答"Yes",结果造成很多BIOS兼容性问题(主要是电源管理方面)。具体故事的细节请到内核源码文件drivers/acpi/osl.c中搜索"The story of _OSI(Linux)"注释。
	此参数用于修改内核中的操作系统接口字符串(_OSI string)列表默认值,这样当BIOS向内核询问:"你是xxx吗?"的时候,内核就可以根据修改后的列表中是否存在"xxx"回答"Yes"或"No"了,主要用于解决BIOS兼容性问题导致的故障(例如屏幕亮度调整)。
	acpi_osi="Linux"表示添加"Linux";
	acpi_osi="!Linux"表示删除"Linux";
	acpi_osi=!* 表示删除所有字符串(v3.13新增),可以和多个acpi_osi="Linux"格式联合使用;
	acpi_osi=! 表示删除所有内置的字符串(v3.13新增),可以和多个acpi_osi="Linux"格式联合使用;
	acpi_osi= 表示禁用所有字符串,仅可单独使用(不能联合使用)。
[HW,ACPI]
acpi_serialize
	强制内核以串行方式执行AML(ACPI Machine Language)字节码。用于解决某些有缺陷的BIOS导致的故障。
[ACPI]
acpi_enforce_resources={strict|lax|no}
	检查驱动程序和ACPI操作区域(SystemIO,SystemMemory)之间资源冲突的方式。
	strict(默认值)禁止任何驱动程序访问已被ACPI声明为"受保护"的操作区域,这是最安全的方式,可以从根本上避免冲突。
	lax允许驱动程序访问已被ACPI声明的保护区域(但会显示一个警告)。这可能会造成冲突,但是可以兼容某些老旧且脑残的驱动程序(例如某些硬件监控驱动)。
	no表示根本不声明任何ACPI保护区域,也就是完全允许任意驱动程序访问ACPI操作区域。
[ACPI]
pnpacpi=off
	禁用ACPI的即插即用功能,转而使用古董的PNPBIOS来代替。

休眠与唤醒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[HW,ACPI]
acpi_sleep={s3_bios,s3_mode,s3_beep,s4_nohwsig,old_ordering,nonvs,sci_force_enable}
	ACPI休眠选项。
	(1)s3_bios和s3_mode与显卡有关。计算机从S3状态(挂起到内存)恢复时,硬件需要被正确的初始化。这对大多数硬件都不是问题,但因为显卡是由BIOS初始化的,内核无法获取必要的恢复信息(仅存在于BIOS中,内核无法读取),所以这里就提供了两个选项,以允许内核通过两种不同的方式来恢复显卡,更多细节请参考Documentation/power/video.txt文档。
	(2)s3_beep主要用于调试,它让PC喇叭在内核的实模式入口点被调用时发出响声。
	(3)s4_nohwsig用于关闭ACPI硬件签名功能,某些有缺陷的BIOS会因为这个原因导致从S4状态(挂起到硬盘)唤醒时失败。
	(4)old_ordering用于兼容古董级的ACPI 1.0 BIOS
	(5)nonvs表示阻止内核在挂起/唤醒过程中保存/恢复ACPI NVS内存信息,主要用于解决某些有缺陷的BIOS导致的挂起/唤醒故障。
	(6)sci_force_enable表示由内核直接设置SCI_EN(ACPI模式开关)的状态,主要用于解决某些有缺陷的BIOS导致的从S1/S3状态唤醒时的故障。
[HIBERNATION]
noresume
	禁用内核的休眠到硬盘功能(CONFIG_HIBERNATION),也就是不从先前的休眠状态中恢复(即使该状态已经被保存在了硬盘的swap分区上),并且清楚先前已经保存的休眠状态(如果有的话)。
[HIBERNATION]
hibernate={noresume|nocompress}
	设置休眠/唤醒属性:
	noresume 表示禁用唤醒,也就是在启动过程中无视任何已经存在的休眠镜像,完全重新启动。
	nocompress 表示禁止对休眠镜像进行压缩/解压。
[HIBERNATION]
resume={ /dev/swap | PARTUUID=uuid | major:minor | hex }
	告诉内核被挂起的内存镜像存放在那个磁盘分区(默认值是CONFIG_PM_STD_PARTITION)。
	假定内存镜像存放在"/dev/sdc15"分区上,该分区的 UUID=0123456789ABCDEF ,其主设备号是"8",次设备号是"47",那么这4种表示法应该分别这样表示:
	resume=/dev/sdc15 (这是内核设备名称,有可能与用户空间的设备名称不同)
	resume=PARTUUID=0123456789ABCDEF
	resume=08:47
	resume=082F
[HIBERNATION]
resume_offset=整数
	指定swap header所在位置的偏移量(单位是PAGE_SIZE),偏移量的计算基准点是"resume="分区的起点。
	仅在使用swap文件(而不是分区)的时候才需要此参数。详见Documentation/power/swsusp-and-swap-files.txt文档
[HIBERNATION]
resumedelay=秒数
	在读取resume文件(设备)之前延迟的秒数,主要用于等待那些反应速度较慢的异步检测的设备就绪(例如USB/MMC)。
[HIBERNATION]
resumewait
	在resume设备没有就绪之前无限等待,主要用于等待那些反应速度较慢的异步检测的设备就绪(例如USB/MMC)。

温度控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[HW,ACPI]
thermal.act=摄氏度
	-1 禁用所有"主动散热"标志点(active trip point)
	正整数 强制设置所有的最低"主动散热"标志点的温度值,单位是摄氏度。
	详见Documentation/thermal/sysfs-api.txt文档。
[HW,ACPI]
thermal.psv=摄氏度
	-1 禁用所有"被动散热"标志点(passive trip point)
	正整数 强制设置所有的"被动散热"标志点的温度值,单位是摄氏度。
	详见Documentation/thermal/sysfs-api.txt文档。
[HW,ACPI]
thermal.crt=摄氏度
	-1 禁用所有"紧急"标志点(critical trip point)
	正整数 强制设置所有的"紧急"标志点的温度值,单位是摄氏度。
	详见Documentation/thermal/sysfs-api.txt文档。
[HW,ACPI]
thermal.nocrt=1
	禁止在ACPI热区(thermal zone)温度达到"紧急"标志点时采取任何动作。
[HW,ACPI]
thermal.off=1
	彻底关闭ACPI热量控制(CONFIG_ACPI_THERMAL)
[HW,ACPI]
thermal.tzp=整数
	设置ACPI热区(thermal zone)的轮询速度:
	0(默认值) 不轮询
	正整数 轮询间隔,单位是十分之一秒。

CPU节能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[KNL]
nohz={on|off}
	启用/禁用内核的dynamic ticks特性。默认值是"on"。
[KNL,BOOT]
nohz_full=CPU列表
	在内核"CONFIG_NO_HZ_FULL=y"的前提下,指定哪些CPU核心可以进入完全无滴答状态。
	"CPU列表"是一个逗号分隔的CPU编号(从0开始计数),也可以使用"-"界定一个范围。例如"0,2,4-7"等价于"0,2,4,5,6,7"
	[注意](1)"boot CPU"(通常都是"0"号CPU)会无条件的从列表中剔除。(2)这里列出的CPU编号必须也要同时列进"rcu_nocbs=..."参数中。
[HW,ACPI]
processor.nocst
	不使用_CST方法检测C-states,而是用老旧的FADT方法检测。
[HW,ACPI]
processor.max_cstate={0|1|2|3|4|5|6|7|8|9}
	无视ACPI表报告的值,强制指定CPU的最大C-state值(必须是一个有效值):C0为正常状态,其他则为不同的省电模式(数字越大表示CPU休眠的程度越深/越省电)。"9"表示无视所有的DMI黑名单限制。
[KNL,HW,ACPI]
intel_idle.max_cstate=[0|正整数]
	设置intel_idle驱动(CONFIG_INTEL_IDLE)允许使用的最大C-state深度。"0"表示禁用intel_idle驱动,转而使用通用的acpi_idle驱动(CONFIG_CPU_IDLE)
idle=poll
idle=halt
idle=nomwait
	对CPU进入休眠状态的额外设置。
	poll 从根本上禁用休眠功能(也就是禁止进入C-states状态),可以略微提升一些CPU性能,但是却需要多消耗许多电力,得不偿失。不推荐使用。
	halt 表示直接使用HALT指令让CPU进入C1/C1E休眠状态,但是不再继续进入C2/C3以及更深的休眠状态。此选项兼容性最好,唤醒速度也最快。但是电力消耗并不最低。
	nomwait 表示进入休眠状态时禁止使用CPU的MWAIT指令。MWAIT是专用于Intel超线程技术的线程同步指令,有助于提升CPU的超线程效能,但对于不具备超线程技术的CPU没有意义。
	[提示]可以同时使用halt和nomwait,也就是"idle=halt idle=nomwait"(但不是:idle=halt,nomwait)
intel_pstate=disable
	禁用 Intel CPU 的 P-state 驱动(CONFIG_X86_INTEL_PSTATE),也就是Intel CPU专用的频率调节器驱动

PCI与PCIE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[PCI]
pci=选项[,选项...]
	指定各种PCI子系统选项:
	earlydump 在内核做出任何改变之前,首先转储出PCI配置空间。主要用于调试目的。
	off 不检测PCI总线,也就是关闭所有PCI设备。
	conf1 强制使用"PCI配置机制1"(目前的事实标准)
	conf2 强制使用"PCI配置机制2"(已被抛弃的老古董)
	noaer [PCIE]禁止使用CONFIG_PCIEAER功能(PCI Express Root Port Advanced Error Reporting)
	nodomains 禁止支持多个PCI root domain(也就是PCI总线域[PCI segment])
	nommconf 禁止使用通过MMCONFIG(CONFIG_PCI_MMCONFIG)方式访问PCI配置空间,MMCONFIG是PCI Express引入的新总线枚举方式。
	check_enable_amd_mmconf 在 AMD family 10h CPU 上检查并启用正确配置的MMIO以访问PCI配置空间
	nomsi [MSI]在全系统范围内禁止MSI中断(CONFIG_PCI_MSI)的使用
	noioapicquirk [APIC]禁止屏蔽任何boot中断(CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS),以确保boot IRQ永远可用。应该永远不需要使用此选项。
	ioapicreroute [APIC]允许将boot IRQ重新路由到主IO-APIC(相当于开启CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS),用于修复某些芯片组bug(在某些情况下会发送多余的"boot IRQ")。
	noioapicreroute [APIC]禁止将boot IRQ重新路由到主IO-APIC(相当于关闭CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS),不建议使用此项。
	rom 为扩展ROM分配地址空间。使用此选项要小心,因为某些设备在ROM与其它资源之间共享地址译码器。
	norom 即使BIOS没有为扩展ROM分配地址空间,也禁止内核为扩展ROM分配地址空间。
	nobar 即使BIOS没有为BAR分配地址空间,也禁止内核为BAR分配地址空间。
	irqmask=0xMMMM 指定允许自动分配到PCI设备的IRQ位掩码,目的是为了避免使用那些被ISA设备占用的IRQ。
	pirqaddr=0xAAAAA 如果PIRQ表(通常情况下由BIOS生成)在F0000h-100000h范围之外,此选项可用于明确指定其物理地址。
	lastbus=N 通过扫描N号总线来扫描全部总线。如果内核不能找到第二条总线,可以通过此方法明确告知其位置。
	assign-busses 总是使用内核自己生成的PCI总线号码替代固件自己生成的值。
	usepirqmask 优先使用可能存在于BIOS $PIR表中的IRQ掩码。某些有缺陷的BIOS需要这个选项(例如HP Pavilion N5400和Omnibook XE3笔记本)。此选项仅在noioapicreroute(相当于关闭CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS)的前提下有效。
	noacpi 不为IRQ路由或PCI扫描使用ACPI
	use_crs 使用来自ACPI的PCI主桥的窗口信息。在2008年之后的BIOS上,这是默认值,如果需要明确使用此项,请当做bug上报开发者。
	nocrs 忽略来自ACPI的PCI主桥的窗口信息,如果需要明确使用此项,请当做bug上报开发者。
	routeirq 对所有PCI设备使用IRQ路由。这个通常是由内核的pci_enable_device()函数完成,所以此项仅为那些忘记调用此函数的驱动提供的临时解决方案。
	skip_isa_align 不对齐ISA IO起始地址,这样就可以处理更多的PCI卡
	noearly 不做任何"early type 1"扫描,这样许多针对主板缺陷的检测将被禁止,同时某些IOMMU驱动也会失效。仅用于解决某些有缺陷的主板故障。
	bfsort 按照宽度优先(breadth-first)的顺序对PCI设备进行排序。目的是为了以与2.4内核兼容的方式获取设备序号。
	nobfsort 不按宽度优先(breadth-first)的顺序对PCI设备进行排序。
	pcie_bus_tune_off 不对PCIe MPS(Max Payload Size)进行调整,而是使用BIOS配置好的默认值。
	pcie_bus_safe 将每个设备的MPS都设为root complex下所有设备支持的MPS中的最大值
	pcie_bus_perf 将设备的MPS设为其上级总线允许的最大MPS,同时将MRRS(Max Read Request Size)设为能支持的最大值(但不能大于设备或总线所支持的MPS值)
	pcie_bus_peer2peer 将每个设备的MPS都设为最安全的"128B",以确保支持所有设备之间的点对点DMA,同时也能保证热插入(hot-added)设备能够正常工作,但代价是可能会造成性能损失。
	cbiosize=nn[KMG] 从CardBus桥的IO窗口中保留的固定长度的总线空间(bus space),默认值是256B。
	cbmemsize=nn[KMG] 从CardBus桥的内存窗口中保留的固定长度的总线空间(bus space),默认值是64MB。
	resource_alignment=[对齐规则@][域:]总线:插槽.功能[; ...] 为重新分配已对齐的内存资源指定对齐方式与设备。如果未指定对齐规则,那么将使用PAGE_SIZE作为对齐规则。也可以通过指定PCI-PCI桥来扩展资源窗口(resource windows)。
	ecrc={bios|on|off} 启用/禁用PCIe ECRC(事务层的端对端CRC校验)。默认值是"bios"(使用BIOS/固件的设定)。
	hpiosize=nn[KMG] 为热插拔桥的IO窗口保留的固定总线空间的大小,默认值是256B。
	hpmemsize=nn[KMG] 为热插拔桥的内存窗口保留的固定总线空间的大小,默认值是2MB。
	realloc={on|off} 当BIOS分配的PCI桥资源太小而无法满足所有子设备的需求时,是否由内核重新分配PCI桥资源。没有默认值(内核的默认值为"undefined")
	realloc 等价于"realloc=on"
	noari 不使用PCIe ARI
	pcie_scan_all 扫描所有可能的PCIe设备。默认只在每个PCIe下游端口扫描一个设备。
[PCIE]
pcie_hp=nomsi
	禁止PCIe本地热插拔使用MSI(CONFIG_PCI_MSI),这将导致所有PCIe端口使用INTx中断提供热插拔服务。
[PCIE]
pcie_ports={auto|native|compat}
	PCIe端口处理方式:
	auto 由BIOS来决定是否使用关联在PCIe端口上的本地PCIe服务(PME, hot-plug, AER)
	native 无条件的使用关联在PCIe端口上的本地PCIe服务(PME, hot-plug, AER)
	compat 禁用PCIe端口驱动,同时将PCIe端口当做PCI-to-PCI桥处理。
[PCIE]
pcie_aspm={off|force}
	强制启用/禁用PCIe Active State Power Management(CONFIG_PCIEASPM)。内核的默认值取决于内核"Default ASPM policy"的配置。
	off 强制禁用
	force 即使设备声明不支持ASPM也强制启用(可能会导致系统锁死)。
[PCIE]
pcie_pme=nomsi
	禁止本地PCIe PME信号使用MSI(CONFIG_PCI_MSI),这将导致所有PCIe root端口使用INTx中断提供所有服务。

LIBATA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[LIBATA]
libata.noacpi
	在libata驱动休眠/唤醒过程中禁止使用ACPI。主要用于解决某些有缺陷的BIOS导致的硬盘假死问题。
[LIBATA]
libata.dma=整数
	控制DMA特性的使用
	libata.dma=0 表示完全禁止所有SATA/PATA端口使用DMA
	libata.dma=1 表示仅允许SATA/PATA硬盘使用DMA
	libata.dma=2 表示仅允许ATAPI(CDROM)使用DMA
	libata.dma=4 表示仅允许CF卡使用DMA
	上述1,2,4实际上是位掩码,可以组合使用,例如 libata.dma=3 表示允许硬盘和CDROM使用DMA,但是禁止CF卡使用DMA
[LIBATA]
libata.ignore_hpa={0|1}
	是否忽略HPA(Host Protected Area)的限制。"0"(默认值)表示不忽略;"1"表示忽略(也就是可以使用整个磁盘空间)
[LIBATA]
libata.force=PORT[.DEVICE]:VAL,PORT[.DEVICE]:VAL,...
	手动强制指定libata的配置。
	其中的"PORT[.DEVICE]"是libata驱动在控制台上以相同格式显示出来的ATA ID字符串(PORT和DEVICE都是十进制数字),下面是两个实例("1.00","2.00"):

	ata1.00: ATAPI: VBOX CD-ROM, 1.0, max UDMA/133
	ata2.00: ATA-6: VBOX HARDDISK, 1.0, max UDMA/133

	如果不指定DEVICE部分,那么就表示适用于该PORT端口上的所有设备。
	VAL部分用来强制设定设备属性:
	40c, 80c, short40c, unk, ign, sata 这些都用于指定线缆类型
	1.5Gbps, 3.0Gbps 这些都用于指定SATA连接速度
	noncq, ncq 关闭还是开启NCQ功能
	dump_id 转储IDENTIFY数据
	pio[0-7], mwdma[0-4], udma[0-7](或者这么写也一样:udma[16,25,33,44,66,100,133]) 数据传输模式
	nohrst, nosrst, norst 只禁止硬重置,只禁止软重置,同时禁止硬重置和软重置
	rstonce 在热拔连接恢复(hot-unplug link recovery)过程中仅尝试一次重置
	atapi_dmadir 开启 ATAPI DMADIR bridge 支持
	disable 禁用该设备

键盘/鼠标/触摸板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[HW]
atkbd.set={2|3}
	设置atkbd驱动(CONFIG_KEYBOARD_ATKBD)的键盘类型:2(默认值)表示AT键盘;3 表示PS/2键盘。
[HW]
atkbd.reset
	在初始化AT或PS/2键盘时重置键盘状态。常用于解决从休眠状态唤醒后键盘失效的故障。
[HW]
atkbd.softraw={0|1}
	当键盘按键被按下时,是返回原始的扫描码(Scancode)还是经过转换之后的键码(Keycode)。常用于解决某些功能键(例如Fn键)故障。
	0 表示返回原始的扫描码(Scancode)
	1(默认值)表示返回转换之后的键码(Keycode)
[USBHID]
usbhid.mousepoll=毫秒数
	USB鼠标的轮询时间间隔,单位是毫秒。默认值是"10",也就是每秒轮询100次,相当于100Hz
[MOUSE]
mousedev.tap_time=毫秒数
	手指触碰和离开触摸板的最大时间间隔,只有小于此间隔的触碰才会被当成鼠标左键单击。此参数仅对工作在绝对模式的触摸板有意义。
[MOUSE]
mousedev.xres=正整数
mousedev.yres=正整数
	触摸板的水平(X)/垂直(Y)方向的分辨率。

USB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[USB]
nousb
	禁用USB子系统(CONFIG_USB_SUPPORT)
[USB]
usbcore.authorized_default={-1|0|1}
	USB设备的默认授权规则:
	-1(默认值) 对除无线USB之外的设备默认授权
	0 对所有设备都默认不授权
	1 对所有设备都默认授权
[USB]
usbcore.autosuspend=秒数
	让USB设备(新检测到的设备以及空闲设备)进入自动休眠前的延迟秒数。默认为2秒。
	如果将秒数设为负数,则表示永不进入自动休眠状态。
[USB]
usbcore.initial_descriptor_timeout=毫秒数
	等待设备回应初始化64位USB_REQ_GET_DESCRIPTOR请求的超时时间。默认值是"5000",也就是5秒。
[USB]
usbcore.blinkenlights={0|1}
	是否让所有的USB集线器(HUB)上的LED指示灯闪烁。默认值"0"表示不闪烁,"1"表示闪烁。
[USB]
usbcore.usbfs_snoop={0|1}
	是否在在日志中记录所有的usbfs traffic信息。默认值"0"表示不记录,"1"表示记录。
[USB]
usbcore.old_scheme_first={0|1}
	是否优先使用老旧的USB设备初始化方法。默认值"0"表示不优先使用。
[USB]
usbcore.use_both_schemes={0|1}
	是否在第一种USB设备初始化方法失败之后,继续尝试第二种方法。默认值"1"表示继续尝试第二种方法。
[USB]
usbcore.usbfs_memory_mb=[0-2047]
	由usbfs分配的缓存上限。取值范围是[0-2047],默认值是"16",单位是"MB"。
[UMS]
usb-storage.delay_use=秒数
	在扫描新USB存储设备上的逻辑单元(Logical Unit)前暂停的秒数。默认值是"5"秒。
[UMS]
usb-storage.quirks=VID:PID:Flags[,VID:PID:Flags]...
	设置一系列的修正项(quirk),用于增补或者改写内核内置的unusual_devs列表内容。该列表用于修正各种有缺陷的USB存储设备的怪毛病。
	多个修正项之间用逗号分隔,修正项的格式是"VID:PID:Flags",其中VID和PID的含义分别是4位16进制数表示的"Vendor ID"与"Product ID"。
	而Flags则是一组字符的组合,其中的每个字符都对应一个具有特定含义的修正(quirk)标记:
	a = SANE_SENSE (收集超过18字节的传感器数据)
	b = BAD_SENSE (不收集超过18字节的传感器数据)
	c = FIX_CAPACITY (无条件的将设备报告的扇区数(容量)减少一个扇区)
	d = NO_READ_DISC_INFO (不使用 READ_DISC_INFO 命令)
	e = NO_READ_CAPACITY_16 (不使用 READ_CAPACITY_16 命令)
	h = CAPACITY_HEURISTICS (如果设备报告的扇区数(容量)是奇数,那么就减少一个扇区)
	i = IGNORE_DEVICE (不绑定此设备)
	l = NOT_LOCKABLE (不要尝试锁定/解锁可弹出媒体)
	m = MAX_SECTORS_64 (每次传输最大不超过64个扇区(32KB)的数据)
	n = INITIAL_READ10 (强制重试初始的 READ(10) 命令(如果最初一次读取失败的话))
	o = CAPACITY_OK (完全信任设备报告的扇区数(容量))
	p = WRITE_CACHE (默认开启设备写入缓存[不怕数据丢失的风险])
	r = IGNORE_RESIDUE (不相信设备报告的[容量]剩余值)
	s = SINGLE_LUN (此设备只有一个逻辑单元(Logical Unit))
	w = NO_WP_DETECT (不检测设备是否有写保护)
	例如:usb-storage.quirks=0419:aaf5:rl,0421:0433:rc
[USB]
uhci-hcd.ignore_oc={0|1}
	是否忽略"电流超限"(overcurrent)事件。
	0(默认值) 不忽略
	1 忽略。某些有缺陷的主板会在USB端口未连接任何设备时,报告很多虚假的"电流超限"事件。设为"1"之后可以避免在内核日志中出现大量的"电流超限"警告,但同时,真实的"电流超限"事件也会被一并忽略。

IOMMU

IOMMU非常类似于MMU,主要有如下功能:(1)IO地址转换[在64位系统上支持32位设备];(2)分散-聚集(scatter-gather)支持[简化驱动程序的编写];(3)DMA重映射与IRQ重映射[简化了IO设备的虚拟化]。

Linux内核当前的DMA映射有如下4种具体实现:
(1)在内存不足3G的机器上,根本不使用任何IOMMU功能,因为根本没必要。内核消息:"PCI-DMA: Disabling IOMMU"
(2)基于GART(CONFIG_GART_IOMMU)的硬件IOMMU。内核消息:"PCI-DMA: using GART IOMMU"
(3)如果内存大于3G同时机器上又没有IOMMU硬件(或者用了"iommu=soft"),那么就使用软件模拟的IOMMU(CONFIG_BOUNCE)。内核消息:"PCI-DMA: Using software bounce buffering for IO (SWIOTLB)“
(4)基于IBM Calgary硬件的IOMMU,仅用于IBM pSeries/xSeries系列服务器。内核消息:"PCI-DMA: Using Calgary IOMMU”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[IOMMU]
iommu={off,force,noforce,soft}
	通用IOMMU设置:
	off 彻底关闭IOMMU功能
	force 强制使用硬件IOMMU,即使硬件可能有缺陷(例如VIA芯片组)或者根本没有必要这样做(例如内存不足3G)。
	noforce(默认) 在内存不足3G的机器上,不使用硬件IOMMU,因为根本没有必要。
	soft(Intel平台的默认值) 使用通过软件模拟的IOMMU(SWIOTLB),同时禁止使用硬件IOMMU(即使存在)。
[IOMMU]
iommu=[SIZE][,allowed][,fullflush|nofullflush][,leak[=NUM]][,memaper[=N]|noaperture][,noagp][,merge|nomerge][,forcesac][,panic][,allowdac|nodac][,calgary]
	仅适用于硬件IOMMU(GART与Calgary)的设置:
	SIZE 重映射区域的大小,单位是字节。
	allowed 含义与"force"相同,即使硬件可能有缺陷(例如VIA芯片组)也强制使用硬件IOMMU
	fullflush(默认) 每次分配时都刷新IOMMU
	nofullflush 不刷新IOMMU
	leak=NUM 开启IOMMU泄漏跟踪(CONFIG_IOMMU_LEAK),NUM是的泄漏页数(默认值是20)。
	memaper=N 在RAM中分配的固有窗口(own aperture)的大小,算法是 2N*32MB,N的默认值是"1",也就是64MB。
	noaperture 禁止IOMMU使用AGP的"aperture"。
	noagp 不初始化AGP驱动,使用完全的"aperture"。
	merge 强制"scatter-gather"合并,隐含了"force",这是一个实验性选项。
	nomerge 禁止"scatter-gather"合并
	forcesac 对于少于40位的掩码强制使用单地址周期(single-address cycle),这是一个实验性选项。
	panic 当IOMMU益处时,允许panic
	allowdac 将32位PCI地址用两个时钟周期推入64位地址,这就是DAC的作用。
	nodac 禁用DAC,也就是所有4GB以上的DMA将强制通过IOMMU(硬件的或模拟的)
	calgary 使用IBM Calgary IOMMU
swiotlb=页数[,force]
	仅适用于软件IOMMU(CONFIG_BOUNCE)的设置:
	页数 为"IO bounce buffer"预先保留的页数,每个页的大小是128K
	force 强制所有IO都透过软件IOMMU
[AMD-IOMMU]
amd_iommu={fullflush|off|force_isolation}
	向AMD IOMMU驱动(CONFIG_AMD_IOMMU)传递参数
	fullflush 表示当IO/TLB项被取消映射的时候立即刷新IO/TLB项(严格模式,速度较慢),否则将仅在IO/TLB项被重用之前进行刷新(宽松模式,速度更快)
	off 表示彻底禁用AMD IOMMU功能
	force_isolation 表示为所有设备强制启用IOMMU隔离(映射),这样IOMMU驱动就不再需要自己去发起隔离请求。注意:此选项不会覆盖"iommu=pt"
[Intel-IOMMU]
intel_iommu={on,off,igfx_off,forcedac,strict,sp_off}
	Intel-IOMMU驱动(CONFIG_INTEL_IOMMU)的主要功能就是DMA重映射,该参数用于设置其特性。
	on 开启Intel-IOMMU驱动
	off 关闭Intel-IOMMU驱动
	igfx_off 关闭Intel集成显卡的DMA重映射功能(默认值为开启)
	forcedac 强制PCI设备使用DAC,而禁止进行地址转换(默认值为允许)
	strict 禁止批量刷写IOTLB(默认值为允许)
	sp_off 关闭super page支持(默认值为开启)
[Intel-IOMMU]
intremap={on,off,nosid,no_x2apic_optout}
	设置中断重映射功能:
	on(默认值)开启中断重映射
	off 关闭中断重映射
	nosid 重映射时不对SID(Source ID)做检查
	no_x2apic_optout 无视BIOS的设置,强制禁用x2APIC特性,主要用于解决某些对x2APIC支持有缺陷的BIOS导致的故障

虚拟化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[PV_OPS]
noreplace-paravirt
	禁止使用内核通用的半虚拟化接口paravirt_ops,主要用于解决某些在Virtual PC上安装或运行Linux的故障。
[VMMIO]
virtio_mmio.device=size@baseaddr:irq[:id]
	实例化virtio-mmio设备(CONFIG_VIRTIO_MMIO)。可以多次使用以实例化多个设备。
	size 大小(可以使用K,M,G后缀)
	baseaddr 物理基准地址(physical base address)
	irq 中断号(将会被传递给request_irq())
	id(可选) platform设备号(device id)
	例子:virtio_mmio.device=1K@0x100b0000:48:7
[KVM]
kvm.ignore_msrs={0|1}
	是否忽略客户机对未经处理的MSR(unhandled MSR)的访问。"0"(默认值)表示不忽略但是会注入#GP;"1"表示忽略。
[KVM]
kvm.mmu_audit={0|1}
	是否允许在运行时对KVM MMU进行审计。"0"(默认值)表示禁止审计;"1"表示允许审计。
[KVM,AMD]
kvm-amd.nested={0|1}
	是否允许嵌套虚拟化(在虚拟机内再创建虚拟机)。"0"表示禁止嵌套;"1"(默认值)表示允许嵌套。
[KVM,AMD]
kvm-amd.npt={0|1}
	是否允许客户机使用嵌套页表(Nested Page Table)。"0"表示禁止使用;"1"(默认值)表示允许使用。
[KVM,Intel]
kvm-intel.ept={0|1}
	是否允许客户机使用扩展页表(Extended Page Table)。"0"表示禁止使用;"1"(默认值)表示允许使用。
[KVM,Intel]
kvm-intel.emulate_invalid_guest_state={0|1}
	是否允许仿真无效的客户机状态。"0"(默认值)表示禁止仿真;"1"表示允许仿真。
[KVM,Intel]
kvm-intel.flexpriority={0|1}
	是否允许使用FlexPriority技术(TPR[Task Priority Register] shadow)。"0"表示禁止使用;"1"(默认值)表示允许使用。
[KVM,Intel]
kvm-intel.nested={0|1}
	是否允许VMX嵌套(nVMX)。"0"(默认值)表示禁止;"1"表示允许。
[KVM,Intel]
kvm-intel.unrestricted_guest={0|1}
	是否允许使用"unrestricted guest"技术。"0"表示禁止使用;"1"(默认值)表示允许使用。
[KVM,Intel]
kvm-intel.vpid={0|1}
	是否允许使用"Virtual Processor Identification"(tagged TLB)技术。"0"表示禁止使用;"1"(默认值)表示允许使用。

内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
[KNL,BOOT]
mem=nn[KMG]
	强制指定内核使用多少数量的内存。仅在你想限定内存使用量时,才需要指定这个参数。同时为了避免PCI设备使用指定范围之外的内存,你还应该配合"memmap="一起使用。
[KNL]
memmap=exactmap
	表示将要使用随后的"memmap=..."等参数进行精确的E820内存映射(因为有时候E820报告的并不准确),同时禁止内核进行任何自动的探测。比如对于一个4G内存的机器可能是:"memmap=exactmap memmap=640K@0 memmap=4095M@1M"。
[KNL]
memmap=nn[KMG]@ss[KMG]
	强制只使用从ss开始的nn长度的特定内存区域。可以多次使用以指定多个区域。
[KNL,ACPI]
memmap=nn[KMG]#ss[KMG]
	强制将从ss开始的nn长度的特定内存区域标记为ACPI数据。
[KNL,ACPI]
memmap=nn[KMG]$ss[KMG]
	强制保留(不使用)从ss开始的nn长度的特定内存区域。
[KNL,BUGS]
reserve=起点,长度[,起点,长度]...
	禁止设备驱动程序自动探测某些iomem区域,因为某些设计不良的硬件会导致自动探测失败或出错。此外,还可以用于人为禁止内核初始化某些端口上的设备。
	内核会将此处指定的iomem区域标记为"reserved"(意为"已经在此处找到设备"),从而将该区域保留。
	因为设备驱动不应该去侦测标记为"reserved"的区域,除非另一个启动参数明确地指示它这样做,所以此参数经常和其它启动参数一起使用:
	用"reserve="保留一段区域禁止所有其他驱动的探测,同时再明确指定一个驱动去检测被保留的区域。例如:

	reserve=0x300,32  blah=0x300

	的意思是:除了允许"blah"驱动探测 0x300 之外,禁止任何其他驱动探测 0x300-0x31f 区域。
	绝大部份的机器都不需要此参数。只有真正有缺陷的硬件或特殊情况才会需要使用它。
	[注意]每个"reserve="参数最多可以指定4个保留区域,如果你有异常复杂的需求,可以使用多重"reserve="来指定。
reservelow=nn[K]
	设置为BIOS保留的底端地址空间数量。
memory_corruption_check={0|1}
	是否开启低位内存脏数据检查(CONFIG_X86_CHECK_BIOS_CORRUPTION)。某些有bug的BIOS经常会在执行系统休眠/唤醒之类动作的时候,破坏内存中前64k的内容。如果始终检查到错误,那么就应该通过"memmap="参数来避免使用这段内存。
memory_corruption_check_size=字节数
	低位内存脏数据检查(CONFIG_X86_CHECK_BIOS_CORRUPTION)的内存范围。默认值是"64K",表示"0-64K"这个内存范围。
memory_corruption_check_period=秒数
	低位内存脏数据检查(CONFIG_X86_CHECK_BIOS_CORRUPTION)的周期。默认值是60秒。设为"0"则表示禁止这种周期性的检查。
[KNL,BOOT]
vmalloc=nn[KMG]
	强制指定vmalloc区域的大小。可用于增加vmalloc区域的最小尺寸(x86默认128MB),也可以用于减少vmalloc的大小,增加更多的空间用于直接映射内核RAM。
[SLUB]
slub_min_order=整数
slub_max_order=整数
	SLUB页块最小与最大order数(默认值分别是"0"与"3"),当然slub_min_order必须小于slub_max_order。每一个slab需要2order个物理页框。过高的值可能会导致内存溢出错误。详见Documentation/vm/slub.txt
[SLUB]
slub_min_objects=整数
	每个slab的最小object总数目(默认值是"4")。详见Documentation/vm/slub.txt
[SLUB]
slub_nomerge
	禁止合并大小相近的多个slab,主要用于调试目的。
[KNL]
dhash_entries=正整数
	设置内核目录项缓存中哈希表默认项数。仅供内核专家使用。
[KNL]
ihash_entries=正整数
	内核会在内存中缓存一定数量的inode结构来加速文件访问,每个inode对应一个文件(不同于文件系统中的inode概念),包含文件访问权限/属主/组/大小/生成时间/访问时间/最后修改时间等信息。这些inode保存在一个哈希表中。
	这个值用于指定这个哈希表的最大项数。你可以根据自己硬盘上可能被访问的文件数量对默认值进行调整(注意需要考虑哈希值的碰撞)。仅供内核专家使用。
[KNL]
transparent_hugepage={always|madvise|never}
	设置透明大内存页(CONFIG_TRANSPARENT_HUGEPAGE)的默认用法:
	always 表示总是对所有应用程序启用透明大内存页支持
	madvise 表示仅对明确要求该特性的程序启用
	never 表示彻底禁用。
	其默认值由内核的编译时设置决定。详见"Documentation/vm/transhuge.txt"文档。
[HW]
default_hugepagesz={2M|1G}
	默认的HugeTLB页大小。若未指定,那么其默认值就是CPU自身的默认值。
	大多数现代计算机体系结构提供对多页面大小的支持,比如X86_64支持4K和2M(要求CPU带有"pse"标记)以及1G(要求CPU带有"pdpe1gb"标记)。
	因此Linux将物理内存划分成许多固定大小的页面(默认为4K),每个页对应一个page结构,这些结构组成一个mem_map[]数组。TLB(Translation Lookaside Buffer)是虚拟地址到物理地址的翻译缓冲区,这种缓冲区在处理器上是很宝贵的,操作系统总是尝试将有限的TLB资源发挥到极致。特别是能够轻松获得若干G内存的时候(大于4G),这种优化就显得尤为关键。而HugeTLB特性则允许将某些页的尺寸增大到2MB或1GB,从而大大减小TLB的尺寸,提高缓冲区的命中率,进而提升内存性能。
[HW]
hugepagesz={2M|1G}
	指定HugeTLB页的大小,通常与"hugepages="联合使用(可使用多次),为不同尺寸的大页分别预留不同的数量。
	例如:hugepagesz=2M hugepages=128 hugepagesz=1G hugepages=8
	注意:1GB的大页只能在命令行上使用"hugepages="预先分配,且分配之后不可在运行时释放。
[HW]
hugepages=正整数
	在启动时分配的HugeTLB页数量,仅在内核开启了CONFIG_HUGETLBFS之后有效。
gbpages
nogbpages
	是否允许内核页表对大小为1GB的Hugepages进行直接映射(CONFIG_DIRECT_GBPAGES)。当"CONFIG_DIRECT_GBPAGES=y"时,默认值是"gbpages"。
vdso={0|1|2}
	vdso=0 禁用VDSO(Virtual Dynamic Shared Object)映射
	vdso=1 启用VDSO(Virtual Dynamic Shared Object)映射,这是"CONFIG_COMPAT_VDSO=n"时的默认值。
	vdso=2 将VDSO(Virtual Dynamic Shared Object)映射到旧式的确定性地址,这是"CONFIG_COMPAT_VDSO=y"时的默认值。
vdso32={0|1|2}
	vdso32=0 禁用32位VDSO(Virtual Dynamic Shared Object)映射
	vdso32=1 启用32位VDSO(Virtual Dynamic Shared Object)映射,这是"CONFIG_COMPAT_VDSO=n"时的默认值。
	vdso32=2 将32位VDSO(Virtual Dynamic Shared Object)映射到旧式的确定性地址,这是"CONFIG_COMPAT_VDSO=y"时的默认值。

MTRR与PAT

1
2
3
4
5
6
7
8
9
10
11
enable_mtrr_cleanup
disable_mtrr_cleanup
	开启/关闭MTRR cleanup(CONFIG_MTRR_SANITIZER)特性。
mtrr_chunk_size=nn[KMG]
	用于"MTRR cleanup"(CONFIG_MTRR_SANITIZER)功能,设置允许的最大连续块尺寸(也就是uncacheable项)。
mtrr_gran_size=nn[KMG]
	用于"MTRR cleanup"(CONFIG_MTRR_SANITIZER)功能,设置MTRR块的粒度(每块的大小)。默认值是"1"。较大的值可以防止小的对齐耗尽MTRR。
mtrr_spare_reg_nr=N
	用于"MTRR cleanup"(CONFIG_MTRR_SANITIZER)功能,设置备用MTRR项的编号。也就是告诉内核reg0N可以被清理或改写(参见"/proc/mtrr"文件),默认值是"1"。
nopat
	禁用PAT支持(CONFIG_X86_PAT)。主要用于解决某PAT故障导致的无法正常启动或者显卡驱动不能正常工作的问题。

图形与显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[AGP]
agp={off|try_unsupported}
	off 表示关闭内核的AGP(CONFIG_AGP)支持;
	try_unsupported 表示尝试驱动那些不受支持的芯片(可能会导致系统崩溃或数据错误)
[HW,DRM]
gamma=浮点数
	设置显示器的Gamma值。
video.brightness_switch_enabled={0|1}
	[背景知识]如果ACPI video.ko驱动(CONFIG_ACPI_VIDEO)能够收到用户通过键盘热键触发的ACPI事件(这需要固件的帮助),video.ko将会把收到的ACPI事件转化为一个"key"类型输入事件,并通过其创建的输入设备发送到用户空间,这样用户空间的工具就可以通过sysfs接口去修改显示器的亮度。这是传统的做法。
	但是从v3.13内核开始,新增了此参数,并且其默认值为"1",表示video.ko驱动除了向用户空间传递事件之外,还要自己在内核层去改变显示器的亮度。
	如果设为"0"则表示不在内核层改变显示器的亮度,依然留给用户层的工具去通过sysfs接口修改。
	详见Documentation/acpi/video_extension.txt文档。
[DRM]
i915.invert_brightness={-1|0|1}
	反转显示器背光亮度控制变量(brightness)的含义。
	通常情况下,brightness的值为"0"表示关闭背光(全黑),随着brightness的值增大到最大值,表示最大亮度。
	但是通过这个参数,可以反转brightness的含义,让"0"表示最亮,而随着brightness值的递增亮度逐渐降低,直到最大值关闭背光(全黑)。
	-1 表示绝不反转其含义,也就是"0"始终表示关闭,最大值始终表示最亮。
	0 表示内核不对此变量的含义加以干预,使用机器自身的默认含义。
	1 表示强制反转其含义,也就是"0"始终表示最亮,最大值始终表示关闭。
	此选项常用于解决某些使用Intel集显/核显(CONFIG_DRM_I915)的电脑在启动时黑屏的问题。
[FB]
logo.nologo
	在系统启动时不显示Linux的企鹅标志图(企鹅数=CPU核心数)

网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[IPV6]
disable_ipv6={0|1}
	是否在所有网络接口上禁用IPv6支持:0(默认值)表示在所有网络接口上开启IPv6支持;1 表示在所有网络接口上关闭IPv6支持。
[IPV6]
autoconf={0|1}
	是否在所有网络接口上开启IPv6地址自动配置。
	0 表示禁止自动配置,这样就只有IPv6回环地址(::1)和"link-local"地址会被自动添加到网络接口上。如果你不想从路由器公告(Router Advertisements)中的地址前缀自动生成IPv6地址,可以使用此项。
	1(默认值) 表示在所有网络接口上开启IPv6地址自动配置
[IP_PNP]
ip=[client-ip:server-ip:gateway-ip:netmask:hostname:device:]autoconf[:dns0-ip:dns1-ip]
	此参数告诉内核如何在启动过程中配置网卡的IP地址及路由表(而不是在启动完成后依赖用户空间的脚本去配置)。仅在内核已启用了CONFIG_IP_PNP的前提下有效。通常用于需要将NFS挂载为根文件系统(CONFIG_ROOT_NFS)的场合。
	此参数有以下4种用法:
	(1)ip={off|none}或者没有使用"ip"参数。这是默认值,表示彻底关闭自动配置功能。
	(2)ip={dhcp|bootp|rarp|any} 表示内核全自动完成所有配置工作(也就是将所有字段设为各自的默认值)。各选项的含义参见下面对autoconf字段的说明。
	(3)将autoconf字段设为{off|none}之一,并明确指定所有其它字段。表示全静态配置,也就是手动指定各字段的值(禁止自动检测)。
	(4)将autoconf字段设为{dhcp|bootp|rarp|any}之一,并明将部分字段留空(字段分割符":"不能省略)。表示半自动配置,也就是将留空的字段设为各自的默认值(自动检测),而将手动指定的字段设为指定的值(禁止自动检测)。
	各字段的说明如下:
	client-ip NFS客户端IP地址。若留空,其默认值将通过自动检测获取。
	server-ip NFS服务器IP地址。该字段仅在需要将NFS挂载为根文件系统(root=/dev/nfs)的时候才是必须的。如果使用RARP检测client-ip并且此字段非空,那么将仅接受指定服务器的应答。若留空,其默认值将通过自动检测获取(也就是自动配置服务器的地址)。
	gateway-ip 网关的IP地址。仅在NFS服务器位于不同子网的时候才是必须的。若留空,其默认值将通过自动检测获取。
	netmask 子网掩码。若留空,其默认值将通过自动检测获取(根据client-ip所属的地址类型[A/B/C之类])。
	hostname NFS客户端的主机名。若留空,其默认值将通过自动检测获取(client-ip的ASCII表示形式)。
	device 使用的网卡。若留空,其默认值将通过自动检测获取:若有多个网卡,那么将通过所有网卡同时发送自动配置请求包,并将最先接收到应答的网卡设为默认网卡。
	autoconf 自动配置方式。{off|none}表示不使用自动配置(必须手动指定个字段的值);{dhcp|bootp|rarp}分别表示只使用DHCP/BOOTP/RARP协议进行自动配置(当然内核必须支持指定的协议);"any"表示使用内核支持的所有自动配置协议(同时发送不同协议的自动配置请求包,以最先接收到的应答为准)。 dns0-ip 主DNS服务器IP地址。若留空,其默认值将通过自动检测获取。其值将通过 /proc/net/pnp 导出到用户空间。在嵌入式系统上,/etc/resolv.conf 常常是到 /proc/net/pnp 的软连接。
	dns1-ip 辅DNS服务器IP地址。其它同上。
[KNL,NET]
rhash_entries=正整数
	设置内核路由缓冲区哈希表的大小,仅供内核网络专家使用。
[KNL,NET]
thash_entries=正整数
	设置内核允许使用的TCP链接哈希表的大小。
[KNL,NET]
uhash_entries=正整数
	设置内核允许使用的UDP/UDP-Lite链接哈希表的大小。
[NETFILTER]
nf_conntrack.acct={0|1}
	是否允许对连接追踪(CONFIG_NF_CONNTRACK)流进行记账。"0"(默认值)表示禁止记账,"1"表示允许记账。

块设备与磁盘阵列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
blkdevparts=
	手动设置块设备分区表(而不是从块设备读取),主要用于嵌入式环境或分区表损坏恢复的场合。详情参见Documentation/block/cmdline-partition.txt文档
[EFI]
gpt
	强制将拥有有效GPT签名但同时又包含无效"保护MBR"的磁盘当做GPT格式的磁盘。
[IOSCHED]
elevator={"bfq"|"cfq"|"deadline"|"noop"}
	指定默认的IO调度器
[LOOP]
loop.max_loop=[0-256]
	在系统启动时无条件的预先创建的回环(loopback)设备数,默认值由CONFIG_BLK_DEV_LOOP_MIN_COUNT决定。如果你使用util-linux-2.21以上版本,建议设为"0"(loop设备将通过/dev/loop-control动态创建)。
[HW,RAID]
raid={autodetect|noautodetect,partitionable|part}
	明确向内核的MD驱动(CONFIG_BLK_DEV_MD)传递RAID配置属性
	autodetect|noautodetect 表示内核是否应该自动检测RAID模式(CONFIG_MD_AUTODETECT)。如果关闭了自动检测,那么必须使用"md="明确告诉内核RAID模式及配置。
	partitionable|part 两者含义相同,都表示内核应该将组装之后得到的RAID设备视为"可分区"设备。
[HW,RAID]
md=N,dev0,dev1,...
	明确向内核的MD驱动(CONFIG_BLK_DEV_MD)传递RAID配置信息,并将列出的设备(dev0,dev1,...)组装为 /dev/mdN 阵列(表现为一个块设备文件)。
	建议仅在根文件系统位于RAID上的情况下使用这个参数。其他非根文件系统的RAID最好在系统启动后(挂载完根之后)再组装。
	N 可以是 0,1,2,3,...,255 中的任意一个整数,表示被创建的md设备的编号,例如:

	md=2,/dev/sda,/dev/sdb,/dev/sdc,/dev/sdd

	表示将 /dev/sda,/dev/sdb,/dev/sdc,/dev/sdd 组装成 /dev/md2 块设备(至于RAID级别之类的信息则由存储在超级块中的元数据提供)。
	[提示]2.6.28之前的老版本内核对创建的阵列还有所谓"可分区阵列"和"不可分区阵列"的区别,具体表现是:如果在N前加上字母"d",则表示所创建的阵列是一个可分区阵列,否则就是不可分区阵列。不过现在已经没有这个区别了,所有创建的阵列都是可分区的,因此"d"也就没有存在的必要了。

根文件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[KNL]
root=字符串
	指定根文件系统的所在位置。通常这是一个必须明确设置的参数。
	"字符串"可以使用如下几种形式:
	XXxx 一个16进制数,其中"XX"是主设备号,"xx"是次设备号。例如"/dev/sdc15"(主设备号是"8",次设备号是"47"),可以表示成"082F"。
	/dev/nfs 表示使用由nfsroot参数指定的NFS磁盘,仅在根文件系统位于NFS文件系统上的时候才使用。
	/dev/disk 表示一块完整的无分区块设备。比如:/dev/md0 /dev/loop0 /dev/sdb /dev/mmcblk0
	/dev/diskN 表示disk磁盘的第N(十进制)个分区。这是最常见的用法,比如:/dev/sda2 /dev/ubda1 /dev/xvda13
	/dev/diskpN 含义与上面的一样,也表示disk磁盘的第N(十进制)个分区,但是用于disk本身以数字结尾的情况(避免混淆)。比如:/dev/md0p3 /dev/emd/0p2 /dev/mmcblk0p1
	PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF 仅用于EFI/GPT格式的磁盘,表示分区表中UUID值为"00112233-4455-6677-8899-AABBCCDDEEFF"的分区。[提示]可以使用blkid查看"PARTUUID"。
	PARTUUID=SSSSSSSS-PP 仅用于传统的MSDOS分区表。"SSSSSSSS"是用16进制表示的32位"NT disk signature","PP"是用16进制表示的分区号。比如:PARTUUID=97531ACF-02 可能相当于 /dev/sda2 
	PARTUUID=XXXX/PARTNROFF=N 表示以UUID="XXXX"的分区为基准,偏移N个分区。假定 /dev/sdb5 的UUID=XXXX,那么 PARTUUID=XXXX/PARTNROFF=3 就表示 /dev/sdb8 ,而 PARTUUID=XXXX/PARTNROFF=-3 则表示 /dev/sdb2
	major:minor 由一对十进制数组成,其中major是主设备号,minor是次设备号。例如"/dev/sdc15"(主设备号是"8",次设备号是"47"),可以表示成"8:47"。
	LABEL=??? 表示卷标为"???"的分区。比如:root=LABEL=/ 。不过这种格式并不被内核直接支持,仅是发行版通过initramfs中的脚本添加了这种格式的支持而已。所以并不通用。
[KNL]
rootfstype=文件系统类型
	指定根文件系统的类型。例如:"xfs"或"ext4"之类
[KNL]
rootflags=挂载选项
	设置根文件系统的挂载选项,比如"noatime,ro"。各种不同的文件系统所能使用的选项各不相同,可以参考 mount 程序的选项。
[KNL]
ro
rw
	以只读(ro)/读写(rw)模式挂载根文件系统
[KNL]
rootdelay=秒数
	在挂载根文件系统前延迟多少秒,主要用于等待那些反应速度较慢的异步检测的设备就绪(例如USB/MMC/FireWire)。
[KNL]
rootwait
	在根文件系统就绪之前无限等待。主要用于等待那些反应速度较慢的异步检测的设备就绪(例如USB/MMC/FireWire)。

系统初始化(init)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
[KNL]
init=文件全路径
	指定内核挂载根文件系统后运行的第一个用户空间程序的绝对路径。默认为"/sbin/init"。
[KNL]
rdinit=全路径
	设置从initramfs中运行的第一个用户空间程序的绝对路径,默认为"/init"。
	[注意]一旦使用了initramfs并且成功的运行了其中的"/init",所有"init"以及与根文件系统相关的参数(包括"nfsroot")对内核而言都将失效。
	initramfs中的脚本必须自己分析各个内核引导参数(/proc/cmdline)并完成根文件系统的挂载与切换,当然也包括启动真正的"init"进程。
[KNL]
S
	以单用户模式运行"init"。注意,这不是一个真正的内核参数,只是给initramfs中的脚本用的。所以并不通用。

NFS(网络文件系统)

[NFS]
lockd.nlm_grace_period=秒数
	为NFS锁管理器指定宽限时间,单位是秒。取值范围在[0-240]?
[NFS]
lockd.nlm_tcpport=端口号
	为NFS锁管理器指定TCP端口
[NFS]
lockd.nlm_timeout=秒数
	为NFS锁管理器指定默认超时时间,单位是秒。默认值是10秒。取值范围在[3-20]?
[NFS]
lockd.nlm_udpport=端口号
	为NFS锁管理器指定UDP端口
[NFS]
nfsroot=[server-ip:]root-dir[,nfs-options]
	指定NFS根文件系统的位置。如果没有设置此参数,那么将使用"/tftpboot/本机IP"(默认值)作为根文件系统,并使用默认的NFS挂载选项。
	server-ip NFS服务器IP地址。其默认值是"ip"参数中的server-ip字段的值。
	root-dir 作为根文件系统挂载的NFS服务器的目录。如果其中包含"%s",那么将会被替换为本机IP地址的ASCII表示形式。
	nfs-options 标准的NFS文件系统挂载选项(例如"ro"),多个选项之间使用逗号分隔。下面是默认使用的值:

	  port     = 由NFS服务器的portmap守护进程给出
	  rsize    = 4096
	  wsize    = 4096
	  timeo    = 7
	  retrans  = 3
	  acregmin = 3
	  acregmax = 60
	  acdirmin = 30
	  acdirmax = 60
	  flags    = hard,nointr,noposix,cto,ac

[NFS]
nfsrootdebug
	在启动过程中,在内核日志里显示详细的NFS相关的调试信息(挂载选项、服务器IP地址、根文件系统路径等),以方便调试和故障诊断。
[NFS]
nfs.callback_tcpport=端口号
	设置NFSv4回复通道(callback channel)监听的TCP端口
[NFS]
nfs.cache_getent=路径
	设置用于更新NFS客户端缓存项的程序的路径。默认值是"/sbin/nfs_cache_getent"。
[NFS]
nfs.cache_getent_timeout=秒数
	尝试更新缓存项超时秒数,超过指定时间仍未更新成功则视为更新失败。默认值是15秒。
[NFS]
nfs.idmap_cache_timeout=秒数
	设置idmapper缓存项的最大寿命,单位是秒。
[NFS]
nfs.enable_ino64={0|1}
	是否开启64位inode号。"0"表示NFS客户端将会为readdir()与stat()系统调用模拟一个32位inode号(而不是返回真实的64位inode号)。"1"(默认值)表示返回真实的64位inode号。
[NFSv4.1]
nfs.max_session_slots=正整数
	设置NFS客户端尝试和服务器端协商的最大会话slot数。这也同时限定了客户端能够像服务器端发送的最大并发RPC请求数。默认值是64。将此值设置为比max_tcp_slot_table_limit大是没有价值的。
[NFSv4]
nfs.nfs4_unique_id=字符串
	指定NFSv4客户端插入到nfs_client_id4字符串中的额外的唯一标识字符串。这通常是一个在系统安装时自动生成的UUID。
[NFSv4.1]
nfs.send_implementation_id={0|1}
	是否在exchange_id请求中包含客户端实现识别信息(implementation identification information)。"0"表示不发送,默认值"1"表示发送。
[NFSv4]
nfs.recover_lost_locks={0|1}
	v3.12新增。是否尝试恢复服务器上由于租约超时而丢失的锁。需要注意的是,这样做很有可能会导致数据错误,因为无法保证超时后的锁文件未被更改。默认值"0"表示不做这样的尝试,而"1"则表示尝试恢复(这是v3.11及之前内核的默认行为)。
[NFSv4]
nfs.nfs4_disable_idmapping={0|1}
	默认值"1"表示在使用了"sec=sys"挂载选项的情况下,RPC身份认证和NFS操作都使用数字化的uid/gid。这会导致idmapping被禁用,从而让NFSv2/v3向NFSv4的迁移变得更加容易。客户端将会自动检测不支持此种操作模式的服务器,并回退到使用idmapper的模式。"0"表示禁止这种行为。
[NFSv4]
nfsd.nfs4_disable_idmapping={0|1}
	默认值"1"表示NFSv4服务器与那些使用auth_sys的客户端之间只使用数字化的uid/gid(包括发送与接收),从而让NFSv2/v3向NFSv4的迁移变得更加容易。"0"表示禁止这种行为。

模块功能

1
2
3
4
5
nomodule
	禁用内核模块加载功能(CONFIG_MODULES)。
[KNL]
module.sig_enforce
	强制内核在加载模块时检查模块签名(CONFIG_MODULE_SIG),并且只接受具有合法签名的模块。如果内核开启了CONFIG_MODULE_SIG_FORCE,那么无论是否使用此参数,都将强制检查模块的签名。

安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
no_file_caps
	要求内核无视文件的权限。这样,执行文件的唯一途径就只有:由root去执行或者setuid root
noexec={on|off}
noexec32={on|off}
	是否允许将某部分内存映射为"禁止执行",这是一种防止数据缓冲区溢出攻击的保护措施(也就是WinXP SP2曾经大力宣传的数据执行保护功能),建议保持默认值"on"。
	[说明]noexec对32bit代码以及64bit代码都有约束力,而noexec32只针对32bit代码。
nosmap
	禁用SMAP(CONFIG_X86_SMAP)支持。SMAP是Intel从Haswell微架构开始引入的一种新特征,用途是禁止内核因为自身错误意外访问用户空间的数据,以避免一些内核漏洞所导致的安全隐患。
nosmep
	禁用SMEP(Supervisor Mode Execution Prevention)支持。SMEP与SMAP类似,也是Intel从Haswell微架构开始引入的一种新特征,用途是禁止内核因为自身错误意外执行用户空间的代码。以避免一些内核漏洞所导致的安全隐患。
nordrand
	即使CPU支持(CONFIG_ARCH_RANDOM),也禁止内核使用RDRAND指令(不过用户空间依然可以使用此指令)。由于很多人怀疑RDRAND指令所依赖的硬件随机数生成器所使用的加密标准(NIST SP800-90)被NSA植入了后门,所以提供了该参数以禁用它,不过大神Torvalds不以为然。
vsyscall={emulate|native|none}
	控制vsyscall系统调用(调用固定的地址0xffffffffff600x00)的行为。大多数静态链接的可执行程序和老旧的Glibc会使用这个系统调用。因为vsyscall始终位于固定的地址,所以很容易被攻击者利用。
	emulate(默认值) 捕捉vsyscalls系统调用,并对其进行安全的模拟。这是比较安全的选项,但效率并不最高。
	native 将vsyscall系统调用直接转变成本地syscall指令,这比模拟方式效率稍微高一些。但是很容易被攻击。
	none 完全禁用vsyscall系统调用。这是最安全的选项,但是有可能会导致系统工作异常。
[EVM]
evm="fix"
	不管当前的完整性状态如何,都允许更新"security.evm"。
[SECURITY]
security={selinux|smack|tomoyo|apparmor|yama}
	选择启用的安全模块。仅在内核同时开启了多个安全模块的情况下才有意义。
[SELINUX]
selinux={0|1}
	是否在启动时就开启SELinux功能(CONFIG_SECURITY_SELINUX_BOOTPARAM):"0"表示关闭,"1"表示开启。
	默认值由内核在编译时确定(CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE)。
	即使设为"1",随后也可以通过 /selinux/disable 在加载安全策略前禁止SELinux功能。
[SELINUX]
enforcing={0|1}
	是否在启动时强制启用SELinux规则。
	"0"(默认值)表示仅仅做记录违规操作日志而不真正拒绝违规操作;
	"1"表示真正拒绝违规操作并做记录违规操作日志。
	该参数还可以在运行时通过 /selinux/enforce 进行修改
[SELINUX]
checkreqprot={0|1}
	设置"checkreqprot"标记的初始值。
	"0"表示由内核强制执行检查保护(包括其中隐含的所有执行保护)
	"1"表示由应用程序自己主动请求执行检查保护
	默认值由内核在编译时确定,也可以在运行时通过 /selinux/checkreqprot 修改
[APPARMOR]
apparmor={0|1}
	是否在启动时就开启AppArmor功能(CONFIG_SECURITY_APPARMOR):"0"表示关闭,"1"表示开启。
	默认值由内核在编译时确定(CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE)。

多CPU与CPU间调度

SMP(对称多处理器)系统中,所有的CPU共享全部资源(总线,内存,I/O等),最大的特点就是所有资源共享,多个CPU之间没有区别。NUMA(非一致内存访问)的基本特征是具有多个CPU节点,每个CPU节点由多个CPU组成,并且具有独立的本地内存与I/O槽口等。因此,虽然每个CPU都可以访问整个系统的内存,但是访问本地节点内存的速度远远高于访问其它节点的内存。详见《SMP/NUMA/MPP体系结构对比》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[SMP]
nosmp
	强制禁用SMP,这是个已被反对使用的旧参数
[SMP]
maxcpus=整数
	最大允许使用的CPU核心数。"0"表示禁用SMP特性(等价于已被反对使用的旧"nosmp"参数),同时也禁用IO APIC;正整数"n"表示最大允许使用n个CPU核心。
[SMP]
nr_cpus=正整数
	允许SMP内核支持的最大CPU核心数(等价于CONFIG_NR_CPUS)。配合CPU热插拔(CONFIG_HOTPLUG_CPU),可在运行时增加CPU数目。
cpu0_hotplug
	强制允许CPU0(boot CPU)热插拔(CONFIG_BOOTPARAM_HOTPLUG_CPU0)。下列特性必须依赖于cpu0,所此参数应谨慎使用:
	(1)从休眠状态(S3,S4)唤醒以及从运行状态进入休眠状态
	(2)PIC中断,也就是某些情况下,关机和重启也会依赖于cpu0
[SMP]
additional_cpus=整数
	最大允许热插拔的CPU数量。默认值由BIOS决定。相见Documentation/x86/x86_64/cpu-hotplug-spec
[NUMA]
numa={off|noacpi}
	off 关闭NUMA支持,也就是让所有内存都只属于同一个节点。
	noacpi 不为NUMA解析ACPI SRAT表
[KNL]
numa_balancing={enable|disable}
	启用/禁用NUMA均衡(CONFIG_NUMA_BALANCING),其默认值由CONFIG_NUMA_BALANCING_DEFAULT_ENABLED决定
[KNL,BOOT]
numa_zonelist_order={zone|node|default}
	设置NUMA的zonelist顺序。这里设置的值还可以在运行中通过sysctl来修改。详见Documentation/sysctl/vm.txt
[KNL,SMP]
isolcpus=CPU编号列表
	将列表中的CPU从内核SMP平衡和调度算法中剔除。
	[注意]提出后并不是绝对不能再使用该CPU的,操作系统仍然可以强制指定特定的进程使用哪个CPU(可以通过taskset来做到)。
	该参数的目的主要是用于实现特定cpu只运行特定进程的目的。
	CPU编号从"0"开始计数,列表的表示方法有三种:
	numA,numB,...,numN
	numA-numN
	以及上述两种表示方法的组合:
	numA,...,numM-numN
	例如:0,3,4-7,9
[KNL,SMP]
relax_domain_level={-1|0|1|2|3|4|5}
	设置CPUSET调度域(sched domain)的默认级别。大于此级别的调度域层次将禁用闲时均衡和唤醒均衡,而其余级别的调度域都开启。
	-1(默认值) 使用系统的默认值(取决于不同的硬件架构)或者由其他的请求确定,也就是不人为指定默认级别。
	0 禁用所有调度域的闲时均衡和唤醒均衡
	1 超线程域(siblings),也就是同一个物理核心内的不同超线程
	2 核域(cores),也就是同一个物理CPU中不同的核心
	3 节点域(node),对于NUMA系统来说就是同一个NUMA节点内,对于non-NUMA系统来说这是整个系统范围
	4 节点组域(chunk of node),仅适用于NUMA系统,表示在一组特定的NUMA节点范围内
	5 全系统(system wide),全部系统范围内
	详见Documentation/cgroups/cpusets.txt文档

控制组(Control Group)

Cgroup(CONFIG_CGROUPS)是一种进程管理机制,也是内核的资源分配框架。

1
2
3
4
5
6
7
8
[KNL]
cgroup_disable="控制器名称"
	禁用cgroup中特定的控制器名称。目前只支持一个"memory"控制器。
noautogroup
	禁止自动创建进程组(CONFIG_SCHED_AUTOGROUP),服务器环境可以考虑使用此参数。
[KNL]
swapaccount={0|1}
	是否统计换入(swap in)内存的资源。"0"表示不统计,"1"表示统计。详见Documentation/cgroups/memory.txt文档。

EFI/UEFI

1
2
3
4
5
6
7
noefi
	禁用EFI支持(CONFIG_EFI)。
[EFI]
add_efi_memmap
	将EFI内存映像包括在内核的可用物理内存映像之中
pstore.backend=efivars
	将"efivars"(CONFIG_EFI_VARS_PSTORE)用作pstore内存文件系统的后端。

杂项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[IP_VS_FTP]
ports=portA,portB,...
	IPVS(IP Virtual Server) FTP帮助模块所使用的端口,最多允许指定8个。默认值是"21"。
io_delay={0x80|0xed|udelay|none}
	设置IO延迟方式
	0x80(CONFIG_IO_DELAY_0X80) 传统的Linux IO延迟方式,久经考验,也最安全
	0xed(CONFIG_IO_DELAY_0XED) 基于0xed端口的IO延迟方式,主要是为了避免和基于0x80端口的主板诊断卡冲突
	udelay(CONFIG_IO_DELAY_UDELAY) 使用内核端udelay()函数作为延迟方法(简单的延迟2微秒).可以不占用任何IO端口空间.
	none(CONFIG_IO_DELAY_NONE) 不使用任何port-IO延迟机制.只要你的机器不是老古董,这个应该是首选.
[KNL]
reboot=[mode][,type][,force]
	指定系统重启的方式:
	mode 用于指定重启模式,可以使用如下2种模式之一:warm(热重启[跳过内存检测]), cold(冷重启[检测并重新初始化所有硬件])
	type 用于指定重启类型,可以使用如下4种类型之一:bios(为热重启使用CPU reboot vector), acpi(优先使用FADT中的ACPI RESET_REG,若失败再转kbd), kbd(使用键盘控制器冷重启,这是默认值), triple, efi(优先使用EFI提供的reset_system运行时服务,若失败再转kbd)
	结尾的"force"表示在重启时不停用其它的CPU,在某些情况下可以让reboot更可靠。
[KNL]
reset_devices
	强制驱动程序在初始化底层设备的过程中重置设备
[KNL]
rcu_nocbs=
	在"CONFIG_RCU_NOCB_CPU=y"的情况下,指定哪些CPU是No-CB CPU
[KNL]
nodelayacct
	禁止在针对每个进程的统计信息中包含进程等候系统资源(cpu,IO同步,内存交换等)所花费的时间,相当于禁用CONFIG_TASK_DELAY_ACCT模块。
[KNL]
sysfs.deprecated={0|1}
	为了兼容旧版本的应用程序而保留过时的sysfs特性(CONFIG_SYSFS_DEPRECATED),其默认值由CONFIG_SYSFS_DEPRECATED_V2确定。

linux内核中异步通知机制--信号处理机制

http://blog.csdn.net/yusiguyuan/article/details/23168363

什么是异步通知:很简单,一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态, 特像硬件上常提的“中断的概念”。 比较准确的说法其实应该叫做“信号驱动的异步I/O”,信号是在软件层次上对中断机制的一种模拟。阻塞I/O意味着一直等待设备可访问再访问,非阻塞I/O意味着使用poll()来查询是否可访问,而异步通知则意味着设备通知应用程序自身可访问。(希望用这么一句话能表达我的意思)

一、系统中存在的异步机制

我认为异步机制是一种理念,并不是某一种具体实现,同步/异步的核心理解应该是如何获取消息的问题,你自身(在计算机中当然是进程本身了)亲自去获取消息,那么就是同步机制,但是如果别人使用某种方式通知你某一个消息,那么你采用的就是异步机制。内核中使用到异步机制的大概有:信号,这是一种进程间通信的异步机制;epoll,这是一种高效处理IO的异步通信机制。也就是从通信和IO两个方面通过不同的方式使用了异步机制。(可能还有别的,暂时不知道)

下面进入正题:

二、信号的基本概念

1)信号的本质

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:
第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。

在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。

2)信号的种类

可以从两个不同的分类角度对信号进行分类:
可靠性方面:可靠信号与不可靠信号;
与时间的关系上:实时信号与非实时信号。

3)可靠信号与不可靠信号

Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。

信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。

信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。

对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

4)实时信号与非实时信号

早期Unix系统只定义了32种信号,前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

5)linux 下信号的生命周期如下:

在目的进程中安装该信号。即是设置捕获该信号时进程进程该执行的操作码。采用signal();sigaction()系统调用来实现。
信号被某个进程产生,同时设置该信号的目的进程(使用pid),之后交给操作系统进行管理。采用kill()、arise()、alarm()等系统调用来实现。
信号在目的进程被注册。信号被添加进进程的PCB(task_struct)中相关的数据结构里——未决信号的数据成员。信号在进程中注册就是把信号值加入到进程的未决信号集里。并且,信号携带的其他信息被保留到未决信的队列的某个sigqueue结构中。
信号在进程中注销。在执行信号处理函数前,要把信号在进程中注销。对于非实时信号(不可靠信号),其在信号未决信号信息链中最多只有一个sigqueue结构,因此该结构被释放后,相应的信号要在未决信号集删除。而实时信号(可靠信号),如果有多个sigqueue,则不会把信号从进程的未决信号集中删除。
信号生命的终结。进程终止当前的工作,保护上下文,执行信号处理函数,之后回复。如果内核是可抢占的,那么还需要调度。

三、信 号 机 制

上 一节中介绍了信号的基本概念,在这一节中,我们将介绍内核如何实现信号机制。即内核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对信 号的反应、内核在什么时机处理和怎样处理进程收到的信号。还要介绍一下setjmp和longjmp在信号中起到的作用。

1、内核对信号的基本处理方法

内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看 该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检 查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。

进程的task_struct结构中有关于本进程中未决信号的数据成员:struct sigpending pending:

1
2
3
4
struct sigpending{
	struct sigqueue *head, *tail;
	sigset_t signal;
};

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

1
2
3
4
struct sigqueue{
	struct sigqueue *next;
	siginfo_t info;
}

信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。

当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。

总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)

内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。

内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退 出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似 的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创 建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时, 才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权 限)。

对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。

当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。

在信号的处理方法中有几点特别要引起注意。

第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清除用户区中设 定的对该信号的处理例程的地址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程 在调用signal之前又得到该信号而导致退出。在BSD中,内核不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢 出。为了避免出现上述情况。在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。

第二个要 引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp,跳出睡眠 状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注 意的是,BSD系统中内核可以自动地重新开始系统调用。

第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。

第四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程 就象没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作(找 出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个 信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCLD的作用仅仅是唤醒一个睡眠在可被 中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。

如果一个进程调用signal系统调用,并设置了SIGCLD的处理方法,并且该进程有子进程处于僵死状态,则内核将向该进程发一个SIGCLD信号。

2、setjmp和longjmp的作用

前面在介绍信号处理机制时,多次提到了setjmp和longjmp,但没有仔细说明它们的作用和实现方法。这里就此作一个简单的介绍。
在 介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是 使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当因为资 源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核 为进程将原先setjmp调用保存在进程用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使 得进程知道该次系统调用失败。这就是它们的作用。

Linux内核CPU负载均衡机制

http://www.oenhan.com/cpu-load-balance

还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在了double_rq_lock函数上,而double_rq_lock则是load_balance触发的,怀疑当时的核间调度出现了问题,在某个负责场景下产生了多核互锁,后面看了一下CPU负载平衡下的代码实现,写一下总结。

内核代码版本:kernel-3.0.13-0.27。

内核代码函数起自load_balance函数,从load_balance函数看引用它的函数可以一直找到schedule函数这里,便从这里开始往下看,在__schedule中有下面一句话。

1
2
if (unlikely(!rq->nr_running))
	idle_balance(cpu, rq);

从上面可以看出什么时候内核会尝试进行CPU负载平衡:即当前CPU运行队列为NULL的时候。

CPU负载平衡有两种方式:pull和push,即空闲CPU从其他忙的CPU队列中拉一个进程到当前CPU队列;或者忙的CPU队列将一个进程推送到空闲的CPU队列中。idle_balance干的则是pull的事情,具体push下面会提到。

在idle_balance里面,有一个proc阀门控制当前CPU是否pull:

1
2
if (this_rq->avg_idle < sysctl_sched_migration_cost)
	return;

sysctl_sched_migration_cost对应proc控制文件是/proc/sys/kernel/sched_migration_cost,开关代表如果CPU队列空闲了500ms(sysctl_sched_migration_cost默认值)以上,则进行pull,否则则返回。

for_each_domain(this_cpu, sd) 则是遍历当前CPU所在的调度域,可以直观的理解成一个CPU组,类似task_group,核间平衡指组内的平衡。负载平衡有一个矛盾就是:负载平衡的频度和CPU cache的命中率是矛盾的,CPU调度域就是将各个CPU分成层次不同的组,低层次搞定的平衡就绝不上升到高层次处理,避免影响cache的命中率。

图例如下;

最终通过load_balance进入正题。

首先通过find_busiest_group获取当前调度域中的最忙的调度组,首先update_sd_lb_stats更新sd的状态,也就是遍历对应的sd,将sds里面的结构体数据填满,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct sd_lb_stats {
	struct sched_group *busiest; /* Busiest group in this sd */
	struct sched_group *this;  /* Local group in this sd */
	unsigned long total_load;  /* Total load of all groups in sd */
	unsigned long total_pwr;   /*   Total power of all groups in sd */
	unsigned long avg_load;    /* Average load across all groups in sd */
 
	/** Statistics of this group */
	unsigned long this_load; //当前调度组的负载
	unsigned long this_load_per_task; //当前调度组的平均负载
	unsigned long this_nr_running; //当前调度组内运行队列中进程的总数
	unsigned long this_has_capacity;
	unsigned int  this_idle_cpus;
 
	/* Statistics of the busiest group */
	unsigned int  busiest_idle_cpus;
	unsigned long max_load; //最忙的组的负载量
	unsigned long busiest_load_per_task; //最忙的组中平均每个任务的负载量
	unsigned long busiest_nr_running; //最忙的组中所有运行队列中进程的个数
	unsigned long busiest_group_capacity;
	unsigned long busiest_has_capacity;
	unsigned int  busiest_group_weight;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
do
{
	local_group = cpumask_test_cpu(this_cpu, sched_group_cpus(sg));
	if (local_group) {
		//如果是当前CPU上的group,则进行赋值
		sds->this_load = sgs.avg_load;
		sds->this = sg;
		sds->this_nr_running = sgs.sum_nr_running;
		sds->this_load_per_task = sgs.sum_weighted_load;
		sds->this_has_capacity = sgs.group_has_capacity;
		sds->this_idle_cpus = sgs.idle_cpus;
	} else if (update_sd_pick_busiest(sd, sds, sg, &sgs, this_cpu)) {
		//在update_sd_pick_busiest判断当前sgs的是否超过了之前的最大值,如果是
		//则将sgs值赋给sds
		sds->max_load = sgs.avg_load;
		sds->busiest = sg;
		sds->busiest_nr_running = sgs.sum_nr_running;
		sds->busiest_idle_cpus = sgs.idle_cpus;
		sds->busiest_group_capacity = sgs.group_capacity;
		sds->busiest_load_per_task = sgs.sum_weighted_load;
		sds->busiest_has_capacity = sgs.group_has_capacity;
		sds->busiest_group_weight = sgs.group_weight;
		sds->group_imb = sgs.group_imb;
	}
	sg = sg->next;
} while (sg != sd->groups);

决定选择调度域中最忙的组的参照标准是该组内所有 CPU上负载(load) 的和, 找到组中找到忙的运行队列的参照标准是该CPU运行队列的长度, 即负载,并且 load 值越大就表示越忙。在平衡的过程中,通过比较当前队列与以前记录的busiest 的负载情况,及时更新这些变量,让 busiest 始终指向域内最忙的一组,以便于查找。

调度域的平均负载计算

1
2
3
sds.avg_load = (SCHED_POWER_SCALE * sds.total_load) / sds.total_pwr;
if (sds.this_load >= sds.avg_load)
	goto out_balanced;

在比较负载大小的过程中, 当发现当前运行的CPU所在的组中busiest为空时,或者当前正在运行的 CPU队列就是最忙的时, 或者当前 CPU队列的负载不小于本组内的平均负载时,或者不平衡的额度不大时,都会返回 NULL 值,即组组之间不需要进行平衡;当最忙的组的负载小于该调度域的平均负载时,只需要进行小范围的负载平衡;当要转移的任务量小于每个进程的平均负载时,如此便拿到了最忙的调度组。

然后find_busiest_queue中找到最忙的调度队列,遍历该组中的所有 CPU 队列,经过依次比较各个队列的负载,找到最忙的那个队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
for_each_cpu(i, sched_group_cpus(group)) {
	/*rq->cpu_power表示所在处理器的计算能力,在函式sched_init初始化时,会把这值设定为SCHED_LOAD_SCALE (=Nice 0的Load Weight=1024).并可透过函式update_cpu_power (in kernel/sched_fair.c)更新这个值.*/
	unsigned long power = power_of(i);
	unsigned long capacity = DIV_ROUND_CLOSEST(power,SCHED_POWER_SCALE);
	unsigned long wl;
	if (!cpumask_test_cpu(i, cpus))
		continue;
 
	rq = cpu_rq(i);
/*获取队列负载cpu_rq(cpu)->load.weight;*/
	wl = weighted_cpuload(i);
 
	/*
	 * When comparing with imbalance, use weighted_cpuload()
	 * which is not scaled with the cpu power.
	 */
	if (capacity && rq->nr_running == 1 && wl > imbalance)
		continue;
 
	/*
	 * For the load comparisons with the other cpu's, consider
	 * the weighted_cpuload() scaled with the cpu power, so that
	 * the load can be moved away from the cpu that is potentially
	 * running at a lower capacity.
	 */
	wl = (wl * SCHED_POWER_SCALE) / power;
 
	if (wl > max_load) {
		max_load = wl;
		busiest = rq;
	}

通过上面的计算,便拿到了最忙队列。
当busiest->nr_running运行数大于1的时候,进行pull操作,pull前对move_tasks,先进行double_rq_lock加锁处理。

1
2
3
4
double_rq_lock(this_rq, busiest);
ld_moved = move_tasks(this_rq, this_cpu, busiest,
		imbalance, sd, idle, &all_pinned);
double_rq_unlock(this_rq, busiest);

move_tasks进程pull task是允许失败的,即move_tasks->balance_tasks,在此处,有sysctl_sched_nr_migrate开关控制进程迁移个数,对应proc的是/proc/sys/kernel/sched_nr_migrate。

下面有can_migrate_task函数检查选定的进程是否可以进行迁移,迁移失败的原因有3个,1.迁移的进程处于运行状态;2.进程被绑核了,不能迁移到目标CPU上;3.进程的cache仍然是hot,此处也是为了保证cache命中率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*关于cache cold的情况下,如果迁移失败的个数太多,仍然进行迁移
 * Aggressive migration if:
 * 1) task is cache cold, or
 * 2) too many balance attempts have failed.
 */
 
tsk_cache_hot = task_hot(p, rq->clock_task, sd);
if (!tsk_cache_hot ||
	sd->nr_balance_failed > sd->cache_nice_tries) {
#ifdef CONFIG_SCHEDSTATS
	if (tsk_cache_hot) {
		schedstat_inc(sd, lb_hot_gained[idle]);
		schedstat_inc(p, se.statistics.nr_forced_migrations);
	}
#endif
	return 1;
}

判断进程cache是否有效,判断条件,进程的运行的时间大于proc控制开关sysctl_sched_migration_cost,对应目录/proc/sys/kernel/sched_migration_cost_ns

1
2
3
4
5
6
7
static int
task_hot(struct task_struct *p, u64 now, struct sched_domain *sd)
{
		s64 delta;
	delta = now - p->se.exec_start;
	return delta < (s64)sysctl_sched_migration_cost;
}

在load_balance中,move_tasks返回失败也就是ld_moved==0,其中sd->nr_balance_failed++对应can_migrate_task中的”too many balance attempts have failed”,然后busiest->active_balance = 1设置,active_balance = 1。

1
2
3
4
5
if (active_balance)
//如果pull失败了,开始触发push操作
stop_one_cpu_nowait(cpu_of(busiest),
	active_load_balance_cpu_stop, busiest,
	&busiest->active_balance_work);

push整个触发操作代码机制比较绕,stop_one_cpu_nowait把active_load_balance_cpu_stop添加到cpu_stopper每CPU变量的任务队列里面,如下:

1
2
3
4
5
6
void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg,
			struct cpu_stop_work *work_buf)
{
	*work_buf = (struct cpu_stop_work){ .fn = fn, .arg = arg, };
	cpu_stop_queue_work(&per_cpu(cpu_stopper, cpu), work_buf);
}

而cpu_stopper则是cpu_stop_init函数通过cpu_stop_cpu_callback创建的migration内核线程,触发任务队列调度。因为migration内核线程是绑定每个核心上的,进程迁移失败的1和3问题就可以通过push解决。active_load_balance_cpu_stop则调用move_one_task函数迁移指定的进程。

上面描述的则是整个pull和push的过程,需要补充的pull触发除了schedule后触发,还有scheduler_tick通过触发中断,调用run_rebalance_domains再调用rebalance_domains触发,不再细数。

1
2
3
4
void __init sched_init(void)
{
	  open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
}

try_to_wake_up函数

try_to_wake_up函数通过把进程状态设置为TASK_RUNNING,并把该进程插入本地CPU运行队列rq来达到唤醒睡眠和停止的进程的目的。
例如:调用该函数唤醒等待队列中的进程,或恢复执行等待信号的进程。该函数接受的参数有:
- 被唤醒进程的描述符指针(p)
- 可以被唤醒的进程状态掩码(state)
- 一个标志(sync),用来禁止被唤醒的进程抢占本地CPU上正在运行的进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync)
{
	int cpu, this_cpu, success = 0;
	unsigned long flags;
	long old_state;
	struct rq *rq;
#ifdef CONFIG_SMP
	struct sched_domain *sd, *this_sd = NULL;
	unsigned long load, this_load;
	int new_cpu;
#endif
	rq = task_rq_lock(p, &flags);
	old_state = p->state;
	if (!(old_state & state))
		goto out;
	if (p->array)
		goto out_running;
	cpu = task_cpu(p);
	this_cpu = smp_processor_id();
#ifdef CONFIG_SMP
... // [多处理器负载平衡工作](/blog/2015/02/11/kernel-sched-balance/)
#endif /* CONFIG_SMP */
	if (old_state == TASK_UNINTERRUPTIBLE) {
		rq->nr_uninterruptible--;
		/*
		 * Tasks on involuntary sleep don't earn
		 * sleep_avg beyond just interactive state.
		 */
		p->sleep_type = SLEEP_NONINTERACTIVE; //简单判断出非交互进程
	} else
		if (old_state & TASK_NONINTERACTIVE)
			p->sleep_type = SLEEP_NONINTERACTIVE;//同上
	activate_task(p, rq, cpu == this_cpu);
	if (!sync || cpu != this_cpu) {
		if (TASK_PREEMPTS_CURR(p, rq))
			resched_task(rq->curr);
	}
	success = 1;
out_running:
	trace_sched_wakeup(rq, p, success);
	p->state = TASK_RUNNING;
out:
	task_rq_unlock(rq, &flags);
	return success;
}

代码解释如下:
1.首先调用task_rq_lock( )禁止本地中断,并获得最后执行进程的CPU(他可能不同于本地CPU)所拥有的运行队列rq的锁。CPU的逻辑号存储在p->thread_info->cpu字段。

2.检查进程的状态p->state是否属于被当作参数传递给函数的状态掩码state,如果不是,就跳到第9步终止函数。

3.如果p->array字段不等于NULL,那么进程已经属于某个运行队列,因此跳转到第8步。

4.在多处理器系统中,该函数检查要被唤醒的进程是否应该从最近运行的CPU的运行队列迁移到另外一个CPU的运行队列。实际上,函数就是根据一些启发式规则选择一个目标运行队列。

5.如果进程处于TASK_UNINTERRUPTIBLE状态,函数递减目标运行队列的nr_uninterruptible字段,并把进程描述符的p->activated字段设置为-1。

6.调用activate_task( )函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void activate_task(struct task_struct *p, struct rq *rq, int local)
{
	unsigned long long now;
	now = sched_clock();
#ifdef CONFIG_SMP
...
#endif
	if (!rt_task(p))
		p->prio = recalc_task_prio(p, now); //计算平均睡眠时间并返回之后的优先级。
	if (p->sleep_type == SLEEP_NORMAL) {
		if (in_interrupt())
			p->sleep_type = SLEEP_INTERRUPTED;
		else {
			p->sleep_type = SLEEP_INTERACTIVE;
		}
	}
	p->timestamp = now;
	__activate_task(p, rq);
}
static void __activate_task(struct task_struct *p, struct rq *rq)
{
	struct prio_array *target = rq->active;
	trace_activate_task(p, rq);
	if (batch_task(p))
		target = rq->expired;
	enqueue_task(p, target);
	inc_nr_running(p, rq);
}

它依次执行下面的子步骤:
a) 调用sched_clock( )获取以纳秒为单位的当前时间戳。如果目标CPU不是本地CPU,就要补偿本地时钟中断的偏差,这是通过使用本地CPU和目标CPU上最近一次发生时钟中断的相对时间戳来达到的:now = (sched_clock( ) - this_rq( )->timestamp_last_tick) + rq->timestamp_last_tick;
b) 调用recalc_task_prio(),把进程描述的指针和上一步计算出的时间戳传递给它。recalc_task_prio()主要更新进程的平均睡眠时间和动态优先级,下一篇博文将详细说明这个函数。
c) 根据下表设置p->activated字段的值,该字段的意义为:
值 说明
0 进程处于TASK_RUNNING 状态。
1 进程处于TASK_INTERRUPTIBLE 或TASK_STOPPED 状态,而且正在被系统调用服务例程或内核线程唤醒。
2 进程处于TASK_INTERRUPTIBLE 或TASK_STOPPED 状态,而且正在被中断处理程序或可延迟函数唤醒。
-1 进程处于TASK_UNINTERRUPTIBLE 状态而且正在被唤醒。 d) 使用在第6a步中计算的时间戳设置p->timestamp字段。
e) 把进程描述符插入活动进程集合:

1
2
enqueue_task(p, rq->active);
rq->nr_running++;

7.如果目标CPU不是本地CPU,或者没有设置sync标志,就检查可运行的新进程的动态优先级是否比rq运行对了中当前进程的动态优先级高(p->prio < rq->curr->prio);如果是,就调用resched_task()抢占rq->curr。在单处理器系统中,后面的函数只是执行set_tsk_need_resched()来设置rq->curr进程的TIF_NEED_RESCHED标志。在多处理器系统中,resched_task()也检查TIF_NEED_RESCHED的旧值是否为0、目标CPU与本地CPU是否不同、rq->curr进程的TIF_POLLING_NRFLAG标志是否清0(目标CPU没有轮询进程TIF_NEED_RESCHED标志的值)。如果是,resched_task()调用smp_send_reschedule()产生IPI,并强制目标CPU重新调度。

8.把进程的p->state字段设置为TASK_RUNNING状态。

9.调用task_rq_unlock()来打开rq运行队列的锁并打开本地中断。

10.返回1(若成功唤醒进程)或0(如果进程没有被唤醒)

内核线程使用

http://blog.csdn.net/newnewman80/article/details/7050090

kthread_create:创建线程。
1
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char *namefmt, ...);

线程创建后,不会马上运行,而是需要将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。

kthread_run :创建并启动线程的函数:
1
struct task_struct *kthread_run(int (*threadfn)(void *data),void *data,const char *namefmt, ...);
kthread_stop:通过发送信号给线程,使之退出。
1
int kthread_stop(struct task_struct *thread);

线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。
但如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止。

1. 头文件

1
2
3
#include <linux/sched.h>       //wake_up_process()
#include <linux/kthread.h>      //kthread_create()、kthread_run()   
#include <err.h>                //IS_ERR()、PTR_ERR()  

2. 实现

2.1创建线程

kernel thread可以用kernel_thread创建,但是在执行函数里面必须用daemonize释放资源并挂到init下,还需要用completion等待这一过程的完成。为了简化操作kthread_create闪亮登场。 在模块初始化时,可以进行线程的创建。使用下面的函数和宏定义:

1
2
3
struct task_struct *kthread_create(int (*threadfn)(void *data),     
					void *data,  
					const char namefmt[], ...);  
1
2
3
4
5
6
7
8
#define kthread_run(threadfn, data, namefmt, ...)                      \
({                                                                     \
	struct task_struct *__k                                            \
		   = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__);  \
	if (!IS_ERR(__k))                                                  \
		   wake_up_process(__k);                                       \
	__k;                                                               \
})  

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static struct task_struct *test_task;  
static int test_init_module(void)  
{  
	int err;  
	test_task = kthread_create(test_thread, NULL, "test_task");  
	if (IS_ERR(test_task)) {  
		printk("Unable to start kernel thread./n");  
		err = PTR_ERR(test_task);  
		test_task = NULL;  
		return err;  
	}  
	wake_up_process(test_task);  
	return 0;  
}  
module_init(test_init_module);  
2.2线程函数

在线程函数里,完成所需的业务逻辑工作。主要框架如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int threadfunc(void *data) {
	...        
	while(1) {
		set_current_state(TASK_UNINTERRUPTIBLE);
		if (kthread_should_stop()) break;
		if () { //条件为真
			//进行业务处理
		} else { //条件为假
			//让出CPU运行其他线程,并在指定的时间内重新被调度
			schedule_timeout(HZ);
		}
	}
	...
	return 0;
}
2.3结束线程

在模块卸载时,可以结束线程的运行。使用下面的函数:

1
int kthread_stop(struct task_struct *k);

例如:

1
2
3
4
5
6
7
8
static void test_cleanup_module(void)  
{  
	if (test_task) {  
		kthread_stop(test_task);  
		test_task = NULL;  
	}  
}  
module_exit(test_cleanup_module);  

设置普通线程优先级

1
2
void set_user_nice(struct task_struct *p, long nice);
// -20 <= nice < 20

将线程设置为实时线程并设置优先级

1
2
3
4
int sched_setscheduler(struct task_struct *p, int policy, struct sched_param *param);
struct sched_param {
	int sched_priority; // 实时线程对应区间[1, 99]
};

CFS 调度模块(在 kernel/sched_fair.c 中实现)用于以下调度策略:SCHED_NORMAL、SCHED_BATCH 和 SCHED_IDLE。
对于 SCHED_RR 和 SCHED_FIFO 策略,将使用实时调度模块(该模块在 kernel/sched_rt.c 中实现)。

top中NI, PR

NI,nice,动态修正CPU调度。范围(-20~19)。越大,cpu调度越一般,越小,cpu调度越偏向它。一般用于后台进程,调整也是往大了调,用来给前台进程让出CPU资源。命令行下可以用renice设置。

PR:优先级,会有两种格式,一种是数字(默认20),一种是RT字符串。

PR默认是20,越小,优先级越高。修改nice可以同时修改PR,测试过程:先开一个窗口,运行wc,另开一个窗口运行top,按N按照PID倒序排,按r输入要renice的PID,然后输入-19~20之间的值,可以看到NI变成输入的值,PR=PR+NI。修改NI得到PR的范围是0~39。优先级由高到低

RT是real-time。只能用chrt -p (1~99) pid来修改。chrt -p 1 1234会将1234的PR改成-2,chrt -p 98 1234变成-99。chrt -p 99 1234会变成RT……只要chrt过,修改nice后PR不会再更改。修改chrt得到的PR范围是RT~-2。优先级由高到低