kk Blog —— 通用基础

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

清理重传队列中函数 tcp_clean_rtx_queue

http://blog.csdn.net/shanshanpt/article/details/22194029

如果重传队列中的一些数据已经被确认,那么, 需要从重传队列中清除出去,需要使用这个函数:tcp_clean_rtx_queue

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/* Remove acknowledged frames from the retransmission queue. If our packet
 * is before the ack sequence we can discard it as it's confirmed to have
 * arrived at the other end.
 */
static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets)
{
	struct tcp_sock *tp = tcp_sk(sk);   // 获得tcp_sock
	const struct inet_connection_sock *icsk = inet_csk(sk); // 获得连接sock
	struct sk_buff *skb;
	u32 now = tcp_time_stamp;           // 当前时间,用于计算RTT
	int fully_acked = 1;                // 表示数据段是否完全被确认
	int flag = 0;
	u32 pkts_acked = 0;
	u32 reord = tp->packets_out;        // 发送出去,还在网络上跑,但是还没有被确认的数据包们
	s32 seq_rtt = -1;
	s32 ca_seq_rtt = -1;
	ktime_t last_ackt = net_invalid_timestamp();    // 把last_ackt设置位0
	// 下面就是遍历sk_write_queue队列,遇到snd_una就停止,如果没有更新过,开始就直接退出了
	while ((skb = tcp_write_queue_head(sk)) && skb != tcp_send_head(sk)) {
		struct tcp_skb_cb *scb = TCP_SKB_CB(skb);   // 获得这个重传队列的一个skb的cb字段
		u32 end_seq;
		u32 acked_pcount;
		u8 sacked = scb->sacked;

		/* Determine how many packets and what bytes were acked, tso and else */
		if (after(scb->end_seq, tp->snd_una)) {     // 注意这个scb是我们发出去的数据的skb中的一个scb哦!,不是接受到的数据!小心
			if (tcp_skb_pcount(skb) == 1 ||         // 这里的意思就是发出去的数据最后一个字节在已经确认的snd_una之后,说明还有没有确认的字节
			!after(tp->snd_una, scb->seq))          // 如果没有设置了TSO 或者 seq不在snd_una之前,即不是 seq---snd_una---end_seq这样情况
				break;                              // 那么说明没有必要把重传元素去掉,(如果是seq---snd_una---end_seq)那么前面半部分的就可以从队列中删除!!!

			acked_pcount = tcp_tso_acked(sk, skb);  // 如果只确认了TSO段中的一部分,则从skb删除已经确认的segs,并统计确认了多少段( 1 )
			if (!acked_pcount)                      // 处理出错
				break;

			fully_acked = 0;                        // 表示TSO只处理了一部分,其他还没处理完
			end_seq = tp->snd_una;
		} else {
			acked_pcount = tcp_skb_pcount(skb);     // 即 !after(scb->end_seq, tp->snd_una),说明已经完全确认OK!
			end_seq = scb->end_seq;
		}

		/* MTU probing checks */
		if (fully_acked && icsk->icsk_mtup.probe_size &&      // 探测mtu,暂时不多说
		!after(tp->mtu_probe.probe_seq_end, scb->end_seq)) {
			tcp_mtup_probe_success(sk, skb);
		}
		// 下面通过sack的信息得到这是一个被重传的过包
		if (sacked & TCPCB_RETRANS) {
			if (sacked & TCPCB_SACKED_RETRANS)      // 如果之前重传过,&& 之前还没收到回复
				tp->retrans_out -= acked_pcount;    // 现在需要更新重传的且没有收到ACK的包
			flag |= FLAG_RETRANS_DATA_ACKED;        // 重传包收到ACK
			ca_seq_rtt = -1;
			seq_rtt = -1;
			if ((flag & FLAG_DATA_ACKED) || (acked_pcount > 1))
				flag |= FLAG_NONHEAD_RETRANS_ACKED;
		} else { // 如果此数据段没有被重传过
			ca_seq_rtt = now - scb->when;           // 通过ACK确认获得RTT值
			last_ackt = skb->tstamp;                // 获得skb的发送时间
			if (seq_rtt < 0) {
				seq_rtt = ca_seq_rtt;
			}
			if (!(sacked & TCPCB_SACKED_ACKED))     // 如果SACK存在一段没有被确认,那么保存其中序号最小号的
				reord = min(pkts_acked, reord);
		}

		if (sacked & TCPCB_SACKED_ACKED)            // 如果是有sack标识
			tp->sacked_out -= acked_pcount;         // 那么更新sack的发出没有接受到确认的数量
		if (sacked & TCPCB_LOST)                    // 如果是丢包标识,那么更新数量
			tp->lost_out -= acked_pcount;

		if (unlikely(tp->urg_mode && !before(end_seq, tp->snd_up)))  // 紧急模式
			tp->urg_mode = 0;

		tp->packets_out -= acked_pcount;            // 发送的包没有确认的数量-=acked_pcount
		pkts_acked += acked_pcount;                 // 接收到确认的包数量+=acked_pcount

		/* Initial outgoing SYN's get put onto the write_queue
		 * just like anything else we transmit.  It is not
		 * true data, and if we misinform our callers that
		 * this ACK acks real data, we will erroneously exit
		 * connection startup slow start one packet too
		 * quickly.  This is severely frowned upon behavior.
		 */
		if (!(scb->flags & TCPCB_FLAG_SYN)) {       // 如果不是SYN握手包
			flag |= FLAG_DATA_ACKED;                // 标识是数据确认
		} else {
			flag |= FLAG_SYN_ACKED;                 // 标识是SYN包标识
			tp->retrans_stamp = 0;                  // 清除重传戳
		}

		if (!fully_acked)                           // 如果TSO段没被完全确认,则到此为止
			break;

		tcp_unlink_write_queue(skb, sk);            // 从发送队列上移除这个skb!!!这个函数其实很简单,其实就是从链表中移除这个skb而已
		sk_wmem_free_skb(sk, skb);                  // 删除skb内存对象
		tcp_clear_all_retrans_hints(tp);
	}                                               // while循环结束

	if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))  // 虚假的SACK
		flag |= FLAG_SACK_RENEGING;

	if (flag & FLAG_ACKED) {                        // 如果ACK更新了数据,是的snd_una更新了
		const struct tcp_congestion_ops *ca_ops
			= inet_csk(sk)->icsk_ca_ops;            // 拥塞信息

		tcp_ack_update_rtt(sk, flag, seq_rtt);      // 更新RTT
		tcp_rearm_rto(sk);                          // 重置超时重传计时器

		if (tcp_is_reno(tp)) {                      // 如果没有SACK处理
			tcp_remove_reno_sacks(sk, pkts_acked);  // 处理乱序的包
		} else {
			/* Non-retransmitted hole got filled? That's reordering */
			if (reord < prior_fackets)
				tcp_update_reordering(sk, tp->fackets_out - reord, 0);  // 更新乱序队列大小
		}

		tp->fackets_out -= min(pkts_acked, tp->fackets_out);    // 更新提前确认算法得出的尚未得到确认的包的数量

		if (ca_ops->pkts_acked) {   // 这是一个钩子函数
			s32 rtt_us = -1;

			/* Is the ACK triggering packet unambiguous? */
			if (!(flag & FLAG_RETRANS_DATA_ACKED)) {            // 如果是确认了非重传的包
				/* High resolution needed and available? */
				if (ca_ops->flags & TCP_CONG_RTT_STAMP &&       // 下面都是测量RTT,精读不同而已
				!ktime_equal(last_ackt,
						 net_invalid_timestamp()))
					rtt_us = ktime_us_delta(ktime_get_real(),
								last_ackt);
				else if (ca_seq_rtt > 0)
					rtt_us = jiffies_to_usecs(ca_seq_rtt);
			}

			ca_ops->pkts_acked(sk, pkts_acked, rtt_us);
		}
	}

#if FASTRETRANS_DEBUG > 0  // 下面用于调试
	BUG_TRAP((int)tp->sacked_out >= 0);
	BUG_TRAP((int)tp->lost_out >= 0);
	BUG_TRAP((int)tp->retrans_out >= 0);
	if (!tp->packets_out && tcp_is_sack(tp)) {
		icsk = inet_csk(sk);
		if (tp->lost_out) {
			printk(KERN_DEBUG "Leak l=%u %d\n",
				tp->lost_out, icsk->icsk_ca_state);
			tp->lost_out = 0;
		}
		if (tp->sacked_out) {
			printk(KERN_DEBUG "Leak s=%u %d\n",
				tp->sacked_out, icsk->icsk_ca_state);
			tp->sacked_out = 0;
		}
		if (tp->retrans_out) {
			printk(KERN_DEBUG "Leak r=%u %d\n",
				tp->retrans_out, icsk->icsk_ca_state);
			 tp->retrans_out = 0;
		}
	}
#endif
	return flag;
}

下面看一下tcp_tso_acked函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* If we get here, the whole TSO packet has not been acked. */
static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb)       // TSO 包并没有全部被确认,现在需要统计已经被确认的数量
{
	struct tcp_sock *tp = tcp_sk(sk);                                // 获得tcp_sock
	u32 packets_acked;

	BUG_ON(!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una));           // seq---end_seq---snd_una  这种情况不可能进来

	packets_acked = tcp_skb_pcount(skb);                             // TSO段总共包括几个
	if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))  // 对于已经确认的部分,更新skb中的信息。例如len之类信息都变了
		return 0;                                                    // 然后重新计算出新的剩余的segs
	packets_acked -= tcp_skb_pcount(skb);                            // 之前总的segs - 现在剩余的segs == 被确认的segs

	if (packets_acked) {
		BUG_ON(tcp_skb_pcount(skb) == 0);
		BUG_ON(!before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq));
	}

	return packets_acked;                                            // 返回被确认的数量
}

linux TCP/IP协议栈-IP层

linux TCP/IP协议栈 —ip_rcv()
linux TCP/IP协议栈 —ip_rcv_finish()
linux TCP/IP协议栈 —ip_local_deliver()
linux TCP/IP协议栈 —ip_local_deliver_finish()
linux TCP/IP协议栈 —ip_defrag()
linux TCP/IP协议栈 —ip_find()
linux TCP/IP协议栈 —inet_frag_find()

ip_rcv()

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
87
88
89
90
91
92
/* 主要功能:对IP头部合法性进行严格检查,然后把具体功能交给ip_rcv_finish。*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
	struct iphdr *iph;
	u32 len;
	/* 网络名字空间,忽略 */
	if (dev->nd_net != &init_net)
		goto drop;
	/*
	 *当网卡处于混杂模式时,收到不是发往该主机的数据包,由net_rx_action()设置。
	 *在调用ip_rcv之前,内核会将该数据包交给嗅探器,所以该函数仅丢弃该包。
	 */
	if (skb->pkt_type == PACKET_OTHERHOST)
		goto drop;
	/* SNMP所需要的统计数据,忽略 */
	IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);

	/*
	 *ip_rcv是由netif_receive_skb函数调用,如果嗅探器或者其他的用户对数据包需要进
	 *进行处理,则在调用ip_rcv之前,netif_receive_skb会增加skb的引用计数,既该引
	 *用计数会大于1。若如此次,则skb_share_check会创建sk_buff的一份拷贝。
	 */
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto out;
	}
	/*
	 *pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个
	 *IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失
	 *的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。
	 */
	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
		goto inhdr_error;
	/* pskb_may_pull可能会调整skb中的指针,所以需要重新定义IP头部*/
	iph = ip_hdr(skb);

	/*
	 *    RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
	 *
	 *    Is the datagram acceptable?
	 *
	 *    1.    Length at least the size of an ip header
	 *    2.    Version of 4
	 *    3.    Checksums correctly. [Speed optimisation for later, skip loopback checksums]
	 *    4.    Doesn't have a bogus length
	 */
	/* 上面说的很清楚了 */
	if (iph->ihl < 5 || iph->version != 4)
		goto inhdr_error;
	/* 确保IP完整的头部包括选项在内存中 */
	if (!pskb_may_pull(skb, iph->ihl*4))
		goto inhdr_error;
	
	iph = ip_hdr(skb);
	/* 验证IP头部的校验和 */
	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
		goto inhdr_error;
	/* IP头部中指示的IP数据包总长度 */
	len = ntohs(iph->tot_len);
	/*
	 *确保skb的数据长度大于等于IP头部中指示的IP数据包总长度及数据包总长度必须
	 *大于等于IP头部长度。
	 */
	if (skb->len < len) {
		IP_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
		goto drop;
	} else if (len < (iph->ihl*4))
		goto inhdr_error;

	/* Our transport medium may have padded the buffer out. Now we know it
	 * is IP we can trim to the true length of the frame.
	 * Note this now means skb->len holds ntohs(iph->tot_len).
	 */
	/* 注释说明的很清楚,该函数成功执行完之后,skb->len = ntohs(iph->tot_len). */
	if (pskb_trim_rcsum(skb, len)) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto drop;
	}

	/* Remove any debris in the socket control block */
	memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
	/* 忽略与netfilter子系统的交互,调用为ip_rcv_finish(skb) */
	return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
		 ip_rcv_finish);

inhdr_error:
	IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
	kfree_skb(skb);
out:
	return NET_RX_DROP;
}

ip_rcv_finish()

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
static int ip_rcv_finish(struct sk_buff *skb)
{
	const struct iphdr *iph = ip_hdr(skb);
	struct rtable *rt;

	/*
	 *    Initialise the virtual path cache for the packet. It describes
	 *    how the packet travels inside Linux networking.
	 */
	/*
	 * 通常从外界接收的数据包,skb->dst不会包含路由信息,暂时还不知道在何处会设置
	 * 这个字段。ip_route_input函数会根据路由表设置路由信息,暂时不考虑路由系统。
	 */
	if (skb->dst == NULL) {
		int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
					 skb->dev);
		if (unlikely(err)) {
			if (err == -EHOSTUNREACH)
				IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
			else if (err == -ENETUNREACH)
				IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);
			goto drop;
		}
	}
/* 更新流量控制所需要的统计数据,忽略 */
#ifdef CONFIG_NET_CLS_ROUTE
	if (unlikely(skb->dst->tclassid)) {
		struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
		u32 idx = skb->dst->tclassid;
		st[idx&0xFF].o_packets++;
		st[idx&0xFF].o_bytes+=skb->len;
		st[(idx>>16)&0xFF].i_packets++;
		st[(idx>>16)&0xFF].i_bytes+=skb->len;
	}
#endif
	/* 如果IP头部大于20字节,则表示IP头部包含IP选项,需要进行选项处理.暂时忽略,毕竟很少用 */
	if (iph->ihl > 5 && ip_rcv_options(skb))
		goto drop;

	/* skb->dst包含路由信息。根据路由类型更新SNMP统计数据 */
	rt = (struct rtable*)skb->dst;
	if (rt->rt_type == RTN_MULTICAST)
		IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
	else if (rt->rt_type == RTN_BROADCAST)
		IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);
	/*
	 * dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的
	 * 函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.
	 * 暂时仅先考虑ip_local_deliver。
	 */
	return dst_input(skb);

drop:
	kfree_skb(skb);
	return NET_RX_DROP;
}

ip_local_deliver()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 *     Deliver IP Packets to the higher protocol layers.
 */
主要功能:收集IP分片,然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议。
int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *    Reassemble IP fragments.
	 */
	/*
	 * 判断该IP数据包是否是一个分片,如果IP_MF置位,则表示该包是分片之一,其
	 * 后还有更多分片,最后一个IP分片未置位IP_MF但是其offset是非0。
	 * 如果是一个IP分片,则调用ip_defrag重新组织IP数据包。
	 */
	if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
		if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}
	/* 调用ip_local_deliver_finish(skb) */
	return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
		 ip_local_deliver_finish);
}

ip_local_deliver_finish()

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
/* 如果忽略掉原始套接字和IPSec,则该函数仅仅是根据IP头部中的协议字段选择上层L4协议,并交给它来处理 */
static int ip_local_deliver_finish(struct sk_buff *skb)
{
	/* 跳过IP头部 */
	__skb_pull(skb, ip_hdrlen(skb));

	/* Point into the IP datagram, just past the header. */
	/* 设置传输层头部位置 */
	skb_reset_transport_header(skb);

	rcu_read_lock();
	{
		/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
		int protocol = ip_hdr(skb)->protocol;
		int hash;
		struct sock *raw_sk;
		struct net_protocol *ipprot;

	resubmit:
	/* 这个hash根本不是哈希值,仅仅只是inet_protos数组中的下表而已 */
		hash = protocol & (MAX_INET_PROTOS - 1);
		raw_sk = sk_head(&raw_v4_htable[hash]);

		/* If there maybe a raw socket we must check - if not we
		 * don't care less
		 */
	/* 原始套接字?? 忽略... */
		if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
			raw_sk = NULL;
	/* 查找注册的L4层协议处理结构。 */
		if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
			int ret;
	/* 启用了安全策略,则交给IPSec */
			if (!ipprot->no_policy) {
				if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					kfree_skb(skb);
					goto out;
				}
				nf_reset(skb);
			}
	/* 调用L4层协议处理函数 */
	/* 通常会是tcp_v4_rcv, udp_rcv, icmp_rcv和igmp_rcv */
	/* 如果注册了其他的L4层协议处理,则会进行相应的调用。 */
			ret = ipprot->handler(skb);
			if (ret < 0) {
				protocol = -ret;
				goto resubmit;
			}
			IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
		} else {
			if (!raw_sk) {    /* 无原始套接字,提交给IPSec */
				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
					icmp_send(skb, ICMP_DEST_UNREACH,
						 ICMP_PROT_UNREACH, 0);
				}
			} else
				IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
			kfree_skb(skb);
		}
	}
 out:
	rcu_read_unlock();

	return 0;
}

ip_defrag()

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
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
	struct ipq *qp;

	IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

	/* Start by cleaning up the memory. */
	/*
	 * 首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值,如果是,则调用
	 * ip_evictor()丢弃未完全到达的IP分片,从最旧的分片开始释放。此举一来是为了节
	 * 约内存,二来是未了防止黑客的恶意攻击。使分片在系统中累计,降低系统性能。
	 */
	if (atomic_read(&ip4_frags.mem) > ip4_frags_ctl.high_thresh)
		ip_evictor();

	/* Lookup (or create) queue header */
	/* 如果该分片是数据报的第一个分片,则ip_find返回一个新的队列来搜集分片,否则
	 * 返回其所属于的分片队列。 */
	if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
		int ret;

		spin_lock(&qp->q.lock);
	/* 将该分片加入到队列中,重组分片队列,如果所有的包都收到了,则该函数
	 * 负责重组IP包 */
		ret = ip_frag_queue(qp, skb);

		spin_unlock(&qp->q.lock);
		ipq_put(qp);    /* 引用计数减1 */
		return ret;
	}

	IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
	kfree_skb(skb);
	return -ENOMEM;
}

ip_find()

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
/* Find the correct entry in the "incomplete datagrams" queue for
 * this IP datagram, and create new one, if nothing is found.
 */
/* u32 user这个参数有点迷惑,其表示以何种理由需要对数据包进行重组,在ip_local_deliver的调用序列当中,这个值是IP_DEFRAG_LOCAL_DELIVER。*/
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
	struct inet_frag_queue *q;
	struct ip4_create_arg arg;
	unsigned int hash;

	arg.iph = iph;
	arg.user = user;
	/*
	 * hash算法,该算法除了使用所给的这四个参数之外,还使用了一个随机值
	 * ip4_frags.rnd,,其初始化为
	 * (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));
	 * 这是为了防止黑客根据固定的hash算法,通过设置ip头部的这些字段,生成同样
	 * HASH值,从而使某一HASH队列长度急剧增大而影响性能。
	 */
	hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
	/* 若存在该分片所属的分片队列则返回这个队列,否则创建一个新的队列 */
	q = inet_frag_find(&ip4_frags, &arg, hash);
	if (q == NULL)
		goto out_nomem;

	return container_of(q, struct ipq, q);

out_nomem:
	LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
	return NULL;
}

inet_frag_find()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct inet_frag_queue *inet_frag_find(struct inet_frags *f, void *key,
		unsigned int hash)
{
	struct inet_frag_queue *q;
	struct hlist_node *n;

	/* f->lock是读写锁,先搜索是否存在该IP分段所属的队列 */
	read_lock(&f->lock);
	hlist_for_each_entry(q, n, &f->hash[hash], list) { /* 扫描该HASH槽中所有节点 */
	/* f->match中match字段在ipfrag_init中初始化为ip4_frag_match函数。*/
	/* 对比分片队列中的散列字段和user是否和key相等,key指向的是struct ip4_create_arg
	 * 结构,包含IP头部和user字段。 */
		if (f->match(q, key)) {
			atomic_inc(&q->refcnt);     /* 若找到,则增加该队列引用计数。 */
			read_unlock(&f->lock);
			return q;                /* 返回该队列 */
		}
	}
	read_unlock(&f->lock);
	/* 该分片是第一个IP分片,创建一个新的分片队列并添加到合适的HASH队列 */
	return inet_frag_create(f, key, hash);
}

Linux slab 分配器,注意RCU

Linux Slab分配器(一)-概述
Linux Slab分配器(二)-初始化
Linux Slab分配器(三)-创建缓存
Linux Slab分配器(四)-分配对象
Linux Slab分配器(五)–释放对象
Linux Slab分配器(六)–创建slab和销毁slab
Linux Slab分配器(七)–销毁缓存

一、概述

slab分配器是Linux内存管理中非常重要和复杂的一部分,其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统。slab分配对象时,会使用最近释放的对象内存块,因此其驻留在CPU高速缓存的概率较高。

用于描述和管理cache的数据结构是struct kmem_cache
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
struct kmem_cache {
/* 1) per-cpu data, touched during every alloc/free */
	/*per-CPU数据,记录了本地高速缓存的信息,也用于跟踪最近释放的对象,每次分配和释放都要直接访问它*/
	struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
	unsigned int batchcount;  /*本地高速缓存转入或转出的大批对象数量*/
	unsigned int limit;       /*本地高速缓存中空闲对象的最大数目*/
	unsigned int shared;

	unsigned int buffer_size;/*管理对象的大小*/
	u32 reciprocal_buffer_size;/*buffer_size的倒数值*/
/* 3) touched by every alloc & free from the backend */

	unsigned int flags;          /* 高速缓存的永久标识*/
	unsigned int num;         /* 一个slab所包含的对象数目 */

/* 4) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	unsigned int gfporder;   /*一个slab包含的连续页框数的对数*/

	/* force GFP flags, e.g. GFP_DMA */
	gfp_t gfpflags;          /*与伙伴系统交互时所提供的分配标识*/

	size_t colour;         /* 颜色的个数*/
	unsigned int colour_off; /* 着色的偏移量 */

	/*如果将slab描述符存储在外部,该指针指向存储slab描述符的cache,
	  否则为NULL*/
	struct kmem_cache *slabp_cache;
	unsigned int slab_size;  /*slab管理区的大小*/
	unsigned int dflags;     /*动态标识*/

	/* constructor func */
	void (*ctor)(void *obj); /*创建高速缓存时的构造函数指针*/

/* 5) cache creation/removal */
	const char *name;         /*高速缓存名*/
	struct list_head next;    /*用于将高速缓存链入cache chain*/

/* 6) statistics */
#ifdef CONFIG_DEBUG_SLAB /*一些用于调试用的变量*/
	unsigned long num_active;
	unsigned long num_allocations;
	unsigned long high_mark;
	unsigned long grown;
	unsigned long reaped;
	unsigned long errors;
	unsigned long max_freeable;
	unsigned long node_allocs;
	unsigned long node_frees;
	unsigned long node_overflow;
	atomic_t allochit;
	atomic_t allocmiss;
	atomic_t freehit;
	atomic_t freemiss;

	/*
	 * If debugging is enabled, then the allocator can add additional
	 * fields and/or padding to every object. buffer_size contains the total
	 * object size including these internal fields, the following two
	 * variables contain the offset to the user object and its size.
	 */
	int obj_offset;
	int obj_size;
#endif /* CONFIG_DEBUG_SLAB */

	/*
	 * We put nodelists[] at the end of kmem_cache, because we want to size
	 * this array to nr_node_ids slots instead of MAX_NUMNODES
	 * (see kmem_cache_init())
	 * We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache
	 * is statically defined, so we reserve the max number of nodes.
	 */
	 /*struct kmem_list3用于组织该高速缓存中的slab*/
	struct kmem_list3 *nodelists[MAX_NUMNODES];
	/*
	 * Do not add fields after nodelists[]
	 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
struct kmem_list3 {
	struct list_head slabs_partial;/*slab链表,包含空闲对象和已分配对象的slab描述符*/
	struct list_head slabs_full;   /*slab链表,只包含非空闲的slab描述符*/
	struct list_head slabs_free;   /*slab链表,只包含空闲的slab描述符*/
	unsigned long free_objects;    /*高速缓存中空闲对象的个数*/
	unsigned int free_limit;       /*空闲对象的上限*/
	unsigned int colour_next;       /*下一个slab使用的颜色*/
	spinlock_t list_lock;
	struct array_cache *shared; /* shared per node */
	struct array_cache **alien; /* on other nodes */
	unsigned long next_reap;    /* updated without locking */
	int free_touched;       /* updated without locking */
};
描述和管理单个slab的结构是struct slab
1
2
3
4
5
6
7
8
struct slab {
	struct list_head list;  /*用于将slab链入kmem_list3的链表*/
	unsigned long colouroff;/*该slab的着色偏移*/
	void *s_mem;            /*指向slab中的第一个对象*/
	unsigned int inuse;     /*已分配出去的对象*/
	kmem_bufctl_t free;     /*下一个空闲对象的下标*/
	unsigned short nodeid;  /*节点标识号*/
};

Linux slab 分配器剖析

http://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/

动态内存管理

内存管理的目标是提供一种方法,为实现各种目的而在各个用户之间实现内存共享。内存管理方法应该实现以下两个功能:
1. 最小化管理内存所需的时间 2. 最大化用于一般应用的可用内存(最小化管理开销)

内存管理实际上是一种关于权衡的零和游戏。您可以开发一种使用少量内存进行管理的算法,但是要花费更多时间来管理可用内存。也可以开发一个算法来有效地管理内存,但却要使用更多的内存。最终,特定应用程序的需求将促使对这种权衡作出选择。

每个内存管理器都使用了一种基于堆的分配策略。在这种方法中,大块内存(称为 堆)用来为用户定义的目的提供内存。当用户需要一块内存时,就请求给自己分配一定大小的内存。堆管理器会查看可用内存的情况(使用特定算法)并返回一块内存。搜索过程中使用的一些算法有 first-fit(在堆中搜索到的第一个满足请求的内存块 )和 best-fit(使用堆中满足请求的最合适的内存块)。当用户使用完内存后,就将内存返回给堆。

这种基于堆的分配策略的根本问题是碎片(fragmentation)。当内存块被分配后,它们会以不同的顺序在不同的时间返回。这样会在堆中留下一些洞,需要花一些时间才能有效地管理空闲内存。这种算法通常具有较高的内存使用效率(分配需要的内存),但是却需要花费更多时间来对堆进行管理。

另外一种方法称为 buddy memory allocation,是一种更快的内存分配技术,它将内存划分为 2 的幂次方个分区,并使用 best-fit 方法来分配内存请求。当用户释放内存时,就会检查 buddy 块,查看其相邻的内存块是否也已经被释放。如果是的话,将合并内存块以最小化内存碎片。这个算法的时间效率更高,但是由于使用 best-fit 方法的缘故,会产生内存浪费。

本文将着重介绍 Linux 内核的内存管理,尤其是 slab 分配提供的机制。

slab 缓存

Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。

Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。

图 1 给出了 slab 结构的高层组织结构。在最高层是 cache_chain,这是一个 slab 缓存的链接列表。这对于 best-fit 算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain 的每个元素都是一个 kmem_cache 结构的引用(称为一个 cache)。它定义了一个要管理的给定大小的对象池。

每个缓存都包含了一个 slabs 列表,这是一段连续的内存块(通常都是页面)。存在 3 种 slab:

slabs_full
完全分配的 slab
slabs_partial
部分分配的 slab
slabs_empty
空 slab,或者没有对象被分配

注意 slabs_empty 列表中的 slab 是进行回收(reaping)的主要备选对象。正是通过此过程,slab 所使用的内存被返回给操作系统供其他用户使用。

slab 列表中的每个 slab 都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和释放的基本元素。注意 slab 是 slab 分配器进行操作的最小分配单位,因此如果需要对 slab 进行扩展,这也就是所扩展的最小值。通常来说,每个 slab 被分配为多个对象。

由于对象是从 slab 中进行分配和释放的,因此单个 slab 可以在 slab 列表之间进行移动。例如,当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab 完全被分配并且有对象被释放后,就从 slabs_full 列表中移动到 slabs_partial 列表中。当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_empty 列表中。

slab 背后的动机

与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化。最后,slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

API 函数

现在来看一下能够创建新 slab 缓存、向缓存中增加内存、销毁缓存的应用程序接口(API)以及 slab 中对对象进行分配和释放操作的函数。

第一个步骤是创建 slab 缓存结构,您可以将其静态创建为:

1
struct struct kmem_cache *my_cachep;

然后其他 slab 缓存函数将使用该引用进行创建、删除、分配等操作。kmem_cache 结构包含了每个中央处理器单元(CPU)的数据、一组可调整的(可以通过 proc 文件系统访问)参数、统计信息和管理 slab 缓存所必须的元素。

kmem_cache_create

内核函数 kmem_cache_create 用来创建一个新缓存。这通常是在内核初始化时执行的,或者在首次加载内核模块时执行。其原型定义如下:

1
2
3
4
5
struct kmem_cache *
kmem_cache_create( const char *name, size_t size, size_t align,
			unsigned long flags;
			void (*ctor)(void*, struct kmem_cache *, unsigned long),
			void (*dtor)(void*, struct kmem_cache *, unsigned long));

name 参数定义了缓存名称,proc 文件系统(在 /proc/slabinfo 中)使用它标识这个缓存。 size 参数指定了为这个缓存创建的对象的大小, align 参数定义了每个对象必需的对齐。 flags 参数指定了为缓存启用的选项。这些标志如表 1 所示。

表 1. kmem_cache_create 的部分选项(在 flags 参数中指定)
选项 说明
SLAB_RED_ZONE 在对象头、尾插入标志,用来支持对缓冲区溢出的检查。
SLAB_POISON 使用一种己知模式填充 slab,允许对缓存中的对象进行监视(对象属对象所有,不过可以在外部进行修改)。
SLAB_HWCACHE_ALIGN 指定缓存对象必须与硬件缓存行对齐。

ctor 和 dtor 参数定义了一个可选的对象构造器和析构器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象时,可以通过构造器进行初始化。

在创建缓存之后, kmem_cache_create 函数会返回对它的引用。注意这个函数并没有向缓存分配任何内存。相反,在试图从缓存(最初为空)分配对象时,refill 操作将内存分配给它。当所有对象都被使用掉时,也可以通过相同的操作向缓存添加内存。

kmem_cache_destroy

内核函数 kmem_cache_destroy 用来销毁缓存。这个调用是由内核模块在被卸载时执行的。在调用这个函数时,缓存必须为空。

1
void kmem_cache_destroy( struct kmem_cache *cachep );
kmem_cache_alloc

要从一个命名的缓存中分配一个对象,可以使用 kmem_cache_alloc 函数。调用者提供了从中分配对象的缓存以及一组标志:

1
void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );

这个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用 cache_alloc_refill 向缓存中增加内存。 kmem_cache_alloc 的 flags 选项与 kmalloc 的 flags 选项相同。表 2 给出了标志选项的部分列表。

表 2. kmem_cache_alloc 和 kmalloc 内核函数的标志选项
标志 说明
GFP_USER 为用户分配内存(这个调用可能会睡眠)。
GFP_KERNEL 从内核 RAM 中分配内存(这个调用可能会睡眠)。
GFP_ATOMIC 使该调用强制处于非睡眠状态(对中断处理程序非常有用)。
GFP_HIGHUSER 从高端内存中分配内存。

kmem_cache_zalloc

内核函数 kmem_cache_zalloc 与 kmem_cache_alloc 类似,只不过它对对象执行 memset 操作,用来在将对象返回调用者之前对其进行清除操作。

kmem_cache_free

要将一个对象释放回 slab,可以使用 kmem_cache_free。调用者提供了缓存引用和要释放的对象。

1
void kmem_cache_free( struct kmem_cache *cachep, void *objp );
kmalloc 和 kfree

内核中最常用的内存管理函数是 kmalloc 和 kfree 函数。这两个函数的原型如下:

1
2
void *kmalloc( size_t size, int flags );
void kfree( const void *objp );

注意在 kmalloc 中,惟一两个参数是要分配的对象的大小和一组标志(请参看 表 2 中的部分列表)。但是 kmalloc 和 kfree 使用了类似于前面定义的函数的 slab 缓存。kmalloc 没有为要从中分配对象的某个 slab 缓存命名,而是循环遍历可用缓存来查找可以满足大小限制的缓存。找到之后,就(使用 __kmem_cache_alloc)分配一个对象。要使用 kfree 释放对象,从中分配对象的缓存可以通过调用 virt_to_cache 确定。这个函数会返回一个缓存引用,然后在 __cache_free 调用中使用该引用释放对象。

其他函数

slab 缓存 API 还提供了其他一些非常有用的函数。 kmem_cache_size 函数会返回这个缓存所管理的对象的大小。您也可以通过调用 kmem_cache_name 来检索给定缓存的名称(在创建缓存时定义)。缓存可以通过释放其中的空闲 slab 进行收缩。这可以通过调用 kmem_cache_shrink 实现。注意这个操作(称为回收)是由内核定期自动执行的(通过 kswapd)。

1
2
3
unsigned int kmem_cache_size( struct kmem_cache *cachep );
const char *kmem_cache_name( struct kmem_cache *cachep );
int kmem_cache_shrink( struct kmem_cache *cachep );

slab 缓存的示例用法

下面的代码片断展示了创建新 slab 缓存、从缓存中分配和释放对象然后销毁缓存的过程。首先,必须要定义一个 kmem_cache 对象,然后对其进行初始化(请参看清单 1)。这个特定的缓存包含 32 字节的对象,并且是硬件缓存对齐的(由标志参数 SLAB_HWCACHE_ALIGN 定义)。
清单 1. 创建新 slab 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static struct kmem_cache *my_cachep;

static void init_my_cache( void )
{

	my_cachep = kmem_cache_create( 
				"my_cache",            /* Name */
				32,                    /* Object Size */
				0,                     /* Alignment */
				SLAB_HWCACHE_ALIGN,    /* Flags */
				NULL, NULL );          /* Constructor/Deconstructor */

	return;
}

使用所分配的 slab 缓存,您现在可以从中分配一个对象了。清单 2 给出了一个从缓存中分配和释放对象的例子。它还展示了两个其他函数的用法。
清单 2. 分配和释放对象

1
2
3
4
5
6
7
8
9
10
11
12
13
int slab_test( void )
{
	void *object;

	printk( "Cache name is %s\n", kmem_cache_name( my_cachep ) );
	printk( "Cache object size is %d\n", kmem_cache_size( my_cachep ) );

	object = kmem_cache_alloc( my_cachep, GFP_KERNEL );
	if (object) {
		kmem_cache_free( my_cachep, object );
	}
	return 0;
}

最后,清单 3 演示了 slab 缓存的销毁。调用者必须确保在执行销毁操作过程中,不要从缓存中分配对象。
清单 3. 销毁 slab 缓存

1
2
3
4
5
static void remove_my_cache( void )
{
	if (my_cachep) kmem_cache_destroy( my_cachep );
	return;
}

slab 的 proc 接口

proc 文件系统提供了一种简单的方法来监视系统中所有活动的 slab 缓存。这个文件称为 /proc/slabinfo,它除了提供一些可以从用户空间访问的可调整参数之外,还提供了有关所有 slab 缓存的详细信息。当前版本的 slabinfo 提供了一个标题,这样输出结果就更具可读性。对于系统中的每个 slab 缓存来说,这个文件提供了对象数量、活动对象数量以及对象大小的信息(除了每个 slab 的对象和页面之外)。另外还提供了一组可调整的参数和 slab 数据。

要调优特定的 slab 缓存,可以简单地向 /proc/slabinfo 文件中以字符串的形式回转 slab 缓存名称和 3 个可调整的参数。下面的例子展示了如何增加 limit 和 batchcount 的值,而保留 shared factor 不变(格式为 “cache name limit batchcount shared factor”):

1
# echo "my_cache 128 64 8" > /proc/slabinfo

limit 字段表示每个 CPU 可以缓存的对象的最大数量。 batchcount 字段是当缓存为空时转换到每个 CPU 缓存中全局缓存对象的最大数量。 shared 参数说明了对称多处理器(Symmetric MultiProcessing,SMP)系统的共享行为。

注意您必须具有超级用户的特权才能在 proc 文件系统中为 slab 缓存调优参数。

SLOB 分配器

对于小型的嵌入式系统来说,存在一个 slab 模拟层,名为 SLOB。这个 slab 的替代品在小型嵌入式 Linux 系统中具有优势,但是即使它保存了 512KB 内存,依然存在碎片和难于扩展的问题。在禁用 CONFIG_SLAB 时,内核会回到这个 SLOB 分配器中。更多信息请参看 参考资料 一节。

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确定。