mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-06 08:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
694
Documentation/zh_CN/CodingStyle
Normal file
694
Documentation/zh_CN/CodingStyle
Normal file
|
@ -0,0 +1,694 @@
|
|||
Chinese translated version of Documentation/CodingStyle
|
||||
|
||||
If you have any comment or update to the content, please post to LKML directly.
|
||||
However, if you have problem communicating in English you can also ask the
|
||||
Chinese maintainer for help. Contact the Chinese maintainer, if this
|
||||
translation is outdated or there is problem with translation.
|
||||
|
||||
Chinese maintainer: Zhang Le <r0bertz@gentoo.org>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/CodingStyle的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接发信到LKML。如果你使用英文交流有困难的话,也可
|
||||
以向中文版维护者求助。如果本翻译更新不及时或者翻译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 张乐 Zhang Le <r0bertz@gentoo.org>
|
||||
中文版翻译者: 张乐 Zhang Le <r0bertz@gentoo.org>
|
||||
中文版校译者: 王聪 Wang Cong <xiyou.wangcong@gmail.com>
|
||||
wheelz <kernel.zeng@gmail.com>
|
||||
管旭东 Xudong Guan <xudong.guan@gmail.com>
|
||||
Li Zefan <lizf@cn.fujitsu.com>
|
||||
Wang Chen <wangchen@cn.fujitsu.com>
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Linux内核代码风格
|
||||
|
||||
这是一个简短的文档,描述了linux内核的首选代码风格。代码风格是因人而异的,而且我
|
||||
不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格,
|
||||
并且我也希望绝大多数其他代码也能遵守这个风格。请在写代码时至少考虑一下本文所述的
|
||||
风格。
|
||||
|
||||
首先,我建议你打印一份GNU代码规范,然后不要读它。烧了它,这是一个具有重大象征性
|
||||
意义的动作。
|
||||
|
||||
不管怎样,现在我们开始:
|
||||
|
||||
|
||||
第一章:缩进
|
||||
|
||||
制表符是8个字符,所以缩进也是8个字符。有些异端运动试图将缩进变为4(乃至2)个字符
|
||||
深,这几乎相当于尝试将圆周率的值定义为3。
|
||||
|
||||
理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的屏幕
|
||||
连续看了20小时之后,你将会发现大一点的缩进会使你更容易分辨缩进。
|
||||
|
||||
现在,有些人会抱怨8个字符的缩进会使代码向右边移动的太远,在80个字符的终端屏幕上
|
||||
就很难读这样的代码。这个问题的答案是,如果你需要3级以上的缩进,不管用何种方式你
|
||||
的代码已经有问题了,应该修正你的程序。
|
||||
|
||||
简而言之,8个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太深的
|
||||
时候可以给你警告。留心这个警告。
|
||||
|
||||
在switch语句中消除多级缩进的首选的方式是让“switch”和从属于它的“case”标签对齐于同
|
||||
一列,而不要“两次缩进”“case”标签。比如:
|
||||
|
||||
switch (suffix) {
|
||||
case 'G':
|
||||
case 'g':
|
||||
mem <<= 30;
|
||||
break;
|
||||
case 'M':
|
||||
case 'm':
|
||||
mem <<= 20;
|
||||
break;
|
||||
case 'K':
|
||||
case 'k':
|
||||
mem <<= 10;
|
||||
/* fall through */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
不要把多个语句放在一行里,除非你有什么东西要隐藏:
|
||||
|
||||
if (condition) do_this;
|
||||
do_something_everytime;
|
||||
|
||||
也不要在一行里放多个赋值语句。内核代码风格超级简单。就是避免可能导致别人误读的表
|
||||
达式。
|
||||
|
||||
除了注释、文档和Kconfig之外,不要使用空格来缩进,前面的例子是例外,是有意为之。
|
||||
|
||||
选用一个好的编辑器,不要在行尾留空格。
|
||||
|
||||
|
||||
第二章:把长的行和字符串打散
|
||||
|
||||
代码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。
|
||||
|
||||
每一行的长度的限制是80列,我们强烈建议您遵守这个惯例。
|
||||
|
||||
长于80列的语句要打散成有意义的片段。每个片段要明显短于原来的语句,而且放置的位置
|
||||
也明显的靠右。同样的规则也适用于有很长参数列表的函数头。长字符串也要打散成较短的
|
||||
字符串。唯一的例外是超过80列可以大幅度提高可读性并且不会隐藏信息的情况。
|
||||
|
||||
void fun(int a, int b, int c)
|
||||
{
|
||||
if (condition)
|
||||
printk(KERN_WARNING "Warning this is a long printk with "
|
||||
"3 parameters a: %u b: %u "
|
||||
"c: %u \n", a, b, c);
|
||||
else
|
||||
next_statement;
|
||||
}
|
||||
|
||||
第三章:大括号和空格的放置
|
||||
|
||||
C语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选择或弃用某种放置策
|
||||
略并没有多少技术上的原因,不过首选的方式,就像Kernighan和Ritchie展示给我们的,是
|
||||
把起始大括号放在行尾,而把结束大括号放在行首,所以:
|
||||
|
||||
if (x is true) {
|
||||
we do y
|
||||
}
|
||||
|
||||
这适用于所有的非函数语句块(if、switch、for、while、do)。比如:
|
||||
|
||||
switch (action) {
|
||||
case KOBJ_ADD:
|
||||
return "add";
|
||||
case KOBJ_REMOVE:
|
||||
return "remove";
|
||||
case KOBJ_CHANGE:
|
||||
return "change";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
不过,有一个例外,那就是函数:函数的起始大括号放置于下一行的开头,所以:
|
||||
|
||||
int function(int x)
|
||||
{
|
||||
body of function
|
||||
}
|
||||
|
||||
全世界的异端可能会抱怨这个不一致性是……呃……不一致的,不过所有思维健全的人都知道(
|
||||
a)K&R是_正确的_,并且(b)K&R是正确的。此外,不管怎样函数都是特殊的(在C语言中
|
||||
,函数是不能嵌套的)。
|
||||
|
||||
注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,也就是do语句中的
|
||||
“while”或者if语句中的“else”,像这样:
|
||||
|
||||
do {
|
||||
body of do-loop
|
||||
} while (condition);
|
||||
|
||||
和
|
||||
|
||||
if (x == y) {
|
||||
..
|
||||
} else if (x > y) {
|
||||
...
|
||||
} else {
|
||||
....
|
||||
}
|
||||
|
||||
理由:K&R。
|
||||
|
||||
也请注意这种大括号的放置方式也能使空(或者差不多空的)行的数量最小化,同时不失可
|
||||
读性。因此,由于你的屏幕上的新行是不可再生资源(想想25行的终端屏幕),你将会有更
|
||||
多的空行来放置注释。
|
||||
|
||||
当只有一个单独的语句的时候,不用加不必要的大括号。
|
||||
|
||||
if (condition)
|
||||
action();
|
||||
|
||||
这点不适用于本身为某个条件语句的一个分支的单独语句。这时需要在两个分支里都使用大
|
||||
括号。
|
||||
|
||||
if (condition) {
|
||||
do_this();
|
||||
do_that();
|
||||
} else {
|
||||
otherwise();
|
||||
}
|
||||
|
||||
3.1:空格
|
||||
|
||||
Linux内核的空格使用方式(主要)取决于它是用于函数还是关键字。(大多数)关键字后
|
||||
要加一个空格。值得注意的例外是sizeof、typeof、alignof和__attribute__,这些关键字
|
||||
某些程度上看起来更像函数(它们在Linux里也常常伴随小括号而使用,尽管在C语言里这样
|
||||
的小括号不是必需的,就像“struct fileinfo info”声明过后的“sizeof info”)。
|
||||
|
||||
所以在这些关键字之后放一个空格:
|
||||
if, switch, case, for, do, while
|
||||
但是不要在sizeof、typeof、alignof或者__attribute__这些关键字之后放空格。例如,
|
||||
s = sizeof(struct file);
|
||||
|
||||
不要在小括号里的表达式两侧加空格。这是一个反例:
|
||||
|
||||
s = sizeof( struct file );
|
||||
|
||||
当声明指针类型或者返回指针类型的函数时,“*”的首选使用方式是使之靠近变量名或者函
|
||||
数名,而不是靠近类型名。例子:
|
||||
|
||||
char *linux_banner;
|
||||
unsigned long long memparse(char *ptr, char **retptr);
|
||||
char *match_strdup(substring_t *s);
|
||||
|
||||
在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:
|
||||
|
||||
= + - < > * / % | & ^ <= >= == != ? :
|
||||
|
||||
但是一元操作符后不要加空格:
|
||||
& * + - ~ ! sizeof typeof alignof __attribute__ defined
|
||||
|
||||
后缀自加和自减一元操作符前不加空格:
|
||||
++ --
|
||||
|
||||
前缀自加和自减一元操作符后不加空格:
|
||||
++ --
|
||||
|
||||
“.”和“->”结构体成员操作符前后不加空格。
|
||||
|
||||
不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后你
|
||||
就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器就不
|
||||
会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就这样产
|
||||
生了。
|
||||
|
||||
当git发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白;不过
|
||||
如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的上下文。
|
||||
|
||||
|
||||
第四章:命名
|
||||
|
||||
C是一个简朴的语言,你的命名也应该这样。和Modula-2和Pascal程序员不同,C程序员不使
|
||||
用类似ThisVariableIsATemporaryCounter这样华丽的名字。C程序员会称那个变量为“tmp”
|
||||
,这样写起来会更容易,而且至少不会令其难于理解。
|
||||
|
||||
不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的名字
|
||||
。称一个全局函数为“foo”是一个难以饶恕的错误。
|
||||
|
||||
全局变量(只有当你真正需要它们的时候再用它)需要有一个具描述性的名字,就像全局函
|
||||
数。如果你有一个可以计算活动用户数量的函数,你应该叫它“count_active_users()”或者
|
||||
类似的名字,你不应该叫它“cntuser()”。
|
||||
|
||||
在函数名中包含函数类型(所谓的匈牙利命名法)是脑子出了问题——编译器知道那些类型而
|
||||
且能够检查那些类型,这样做只能把程序员弄糊涂了。难怪微软总是制造出有问题的程序。
|
||||
|
||||
本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计数器
|
||||
,它应该被称为“i”。叫它“loop_counter”并无益处,如果它没有被误解的可能的话。类似
|
||||
的,“tmp”可以用来称呼任意类型的临时变量。
|
||||
|
||||
如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综合症
|
||||
。请看第六章(函数)。
|
||||
|
||||
|
||||
第五章:Typedef
|
||||
|
||||
不要使用类似“vps_t”之类的东西。
|
||||
|
||||
对结构体和指针使用typedef是一个错误。当你在代码里看到:
|
||||
|
||||
vps_t a;
|
||||
|
||||
这代表什么意思呢?
|
||||
|
||||
相反,如果是这样
|
||||
|
||||
struct virtual_container *a;
|
||||
|
||||
你就知道“a”是什么了。
|
||||
|
||||
很多人认为typedef“能提高可读性”。实际不是这样的。它们只在下列情况下有用:
|
||||
|
||||
(a) 完全不透明的对象(这种情况下要主动使用typedef来隐藏这个对象实际上是什么)。
|
||||
|
||||
例如:“pte_t”等不透明对象,你只能用合适的访问函数来访问它们。
|
||||
|
||||
注意!不透明性和“访问函数”本身是不好的。我们使用pte_t等类型的原因在于真的是
|
||||
完全没有任何共用的可访问信息。
|
||||
|
||||
(b) 清楚的整数类型,如此,这层抽象就可以帮助消除到底是“int”还是“long”的混淆。
|
||||
|
||||
u8/u16/u32是完全没有问题的typedef,不过它们更符合类别(d)而不是这里。
|
||||
|
||||
再次注意!要这样做,必须事出有因。如果某个变量是“unsigned long“,那么没有必要
|
||||
|
||||
typedef unsigned long myflags_t;
|
||||
|
||||
不过如果有一个明确的原因,比如它在某种情况下可能会是一个“unsigned int”而在
|
||||
其他情况下可能为“unsigned long”,那么就不要犹豫,请务必使用typedef。
|
||||
|
||||
(c) 当你使用sparse按字面的创建一个新类型来做类型检查的时候。
|
||||
|
||||
(d) 和标准C99类型相同的类型,在某些例外的情况下。
|
||||
|
||||
虽然让眼睛和脑筋来适应新的标准类型比如“uint32_t”不需要花很多时间,可是有些
|
||||
人仍然拒绝使用它们。
|
||||
|
||||
因此,Linux特有的等同于标准类型的“u8/u16/u32/u64”类型和它们的有符号类型是被
|
||||
允许的——尽管在你自己的新代码中,它们不是强制要求要使用的。
|
||||
|
||||
当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选择。
|
||||
|
||||
(e) 可以在用户空间安全使用的类型。
|
||||
|
||||
在某些用户空间可见的结构体里,我们不能要求C99类型而且不能用上面提到的“u32”
|
||||
类型。因此,我们在与用户空间共享的所有结构体中使用__u32和类似的类型。
|
||||
|
||||
可能还有其他的情况,不过基本的规则是永远不要使用typedef,除非你可以明确的应用上
|
||||
述某个规则中的一个。
|
||||
|
||||
总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们就不
|
||||
应该是一个typedef。
|
||||
|
||||
|
||||
第六章:函数
|
||||
|
||||
函数应该简短而漂亮,并且只完成一件事情。函数应该可以一屏或者两屏显示完(我们都知
|
||||
道ISO/ANSI屏幕大小是80x24),只做一件事情,而且把它做好。
|
||||
|
||||
一个函数的最大长度是和该函数的复杂度和缩进级数成反比的。所以,如果你有一个理论上
|
||||
很简单的只有一个很长(但是简单)的case语句的函数,而且你需要在每个case里做很多很
|
||||
小的事情,这样的函数尽管很长,但也是可以的。
|
||||
|
||||
不过,如果你有一个复杂的函数,而且你怀疑一个天分不是很高的高中一年级学生可能甚至
|
||||
搞不清楚这个函数的目的,你应该严格的遵守前面提到的长度限制。使用辅助函数,并为之
|
||||
取个具描述性的名字(如果你觉得它们的性能很重要的话,可以让编译器内联它们,这样的
|
||||
效果往往会比你写一个复杂函数的效果要好。)
|
||||
|
||||
函数的另外一个衡量标准是本地变量的数量。此数量不应超过5-10个,否则你的函数就有
|
||||
问题了。重新考虑一下你的函数,把它分拆成更小的函数。人的大脑一般可以轻松的同时跟
|
||||
踪7个不同的事物,如果再增多的话,就会糊涂了。即便你聪颖过人,你也可能会记不清你2
|
||||
个星期前做过的事情。
|
||||
|
||||
在源文件里,使用空行隔开不同的函数。如果该函数需要被导出,它的EXPORT*宏应该紧贴
|
||||
在它的结束大括号之下。比如:
|
||||
|
||||
int system_is_up(void)
|
||||
{
|
||||
return system_state == SYSTEM_RUNNING;
|
||||
}
|
||||
EXPORT_SYMBOL(system_is_up);
|
||||
|
||||
在函数原型中,包含函数名和它们的数据类型。虽然C语言里没有这样的要求,在Linux里这
|
||||
是提倡的做法,因为这样可以很简单的给读者提供更多的有价值的信息。
|
||||
|
||||
|
||||
第七章:集中的函数退出途径
|
||||
|
||||
虽然被某些人声称已经过时,但是goto语句的等价物还是经常被编译器所使用,具体形式是
|
||||
无条件跳转指令。
|
||||
|
||||
当一个函数从多个位置退出并且需要做一些通用的清理工作的时候,goto的好处就显现出来
|
||||
了。
|
||||
|
||||
理由是:
|
||||
|
||||
- 无条件语句容易理解和跟踪
|
||||
- 嵌套程度减小
|
||||
- 可以避免由于修改时忘记更新某个单独的退出点而导致的错误
|
||||
- 减轻了编译器的工作,无需删除冗余代码;)
|
||||
|
||||
int fun(int a)
|
||||
{
|
||||
int result = 0;
|
||||
char *buffer = kmalloc(SIZE);
|
||||
|
||||
if (buffer == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (condition1) {
|
||||
while (loop1) {
|
||||
...
|
||||
}
|
||||
result = 1;
|
||||
goto out;
|
||||
}
|
||||
...
|
||||
out:
|
||||
kfree(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
第八章:注释
|
||||
|
||||
注释是好的,不过有过度注释的危险。永远不要在注释里解释你的代码是如何运作的:更好
|
||||
的做法是让别人一看你的代码就可以明白,解释写的很差的代码是浪费时间。
|
||||
|
||||
一般的,你想要你的注释告诉别人你的代码做了什么,而不是怎么做的。也请你不要把注释
|
||||
放在一个函数体内部:如果函数复杂到你需要独立的注释其中的一部分,你很可能需要回到
|
||||
第六章看一看。你可以做一些小注释来注明或警告某些很聪明(或者槽糕)的做法,但不要
|
||||
加太多。你应该做的,是把注释放在函数的头部,告诉人们它做了什么,也可以加上它做这
|
||||
些事情的原因。
|
||||
|
||||
当注释内核API函数时,请使用kernel-doc格式。请看
|
||||
Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc以获得详细信息。
|
||||
|
||||
Linux的注释风格是C89“/* ... */”风格。不要使用C99风格“// ...”注释。
|
||||
|
||||
长(多行)的首选注释风格是:
|
||||
|
||||
/*
|
||||
* This is the preferred style for multi-line
|
||||
* comments in the Linux kernel source code.
|
||||
* Please use it consistently.
|
||||
*
|
||||
* Description: A column of asterisks on the left side,
|
||||
* with beginning and ending almost-blank lines.
|
||||
*/
|
||||
|
||||
注释数据也是很重要的,不管是基本类型还是衍生类型。为了方便实现这一点,每一行应只
|
||||
声明一个数据(不要使用逗号来一次声明多个数据)。这样你就有空间来为每个数据写一段
|
||||
小注释来解释它们的用途了。
|
||||
|
||||
|
||||
第九章:你已经把事情弄糟了
|
||||
|
||||
这没什么,我们都是这样。可能你的使用了很长时间Unix的朋友已经告诉你“GNU emacs”能
|
||||
自动帮你格式化C源代码,而且你也注意到了,确实是这样,不过它所使用的默认值和我们
|
||||
想要的相去甚远(实际上,甚至比随机打的还要差——无数个猴子在GNU emacs里打字永远不
|
||||
会创造出一个好程序)(译注:请参考Infinite Monkey Theorem)
|
||||
|
||||
所以你要么放弃GNU emacs,要么改变它让它使用更合理的设定。要采用后一个方案,你可
|
||||
以把下面这段粘贴到你的.emacs文件里。
|
||||
|
||||
(defun linux-c-mode ()
|
||||
"C mode with adjusted defaults for use with the Linux kernel."
|
||||
(interactive)
|
||||
(c-mode)
|
||||
(c-set-style "K&R")
|
||||
(setq tab-width 8)
|
||||
(setq indent-tabs-mode t)
|
||||
(setq c-basic-offset 8))
|
||||
|
||||
这样就定义了M-x linux-c-mode命令。当你hack一个模块的时候,如果你把字符串
|
||||
-*- linux-c -*-放在头两行的某个位置,这个模式将会被自动调用。如果你希望在你修改
|
||||
/usr/src/linux里的文件时魔术般自动打开linux-c-mode的话,你也可能需要添加
|
||||
|
||||
(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$" . linux-c-mode)
|
||||
auto-mode-alist))
|
||||
|
||||
到你的.emacs文件里。
|
||||
|
||||
不过就算你尝试让emacs正确的格式化代码失败了,也并不意味着你失去了一切:还可以用“
|
||||
indent”。
|
||||
|
||||
不过,GNU indent也有和GNU emacs一样有问题的设定,所以你需要给它一些命令选项。不
|
||||
过,这还不算太糟糕,因为就算是GNU indent的作者也认同K&R的权威性(GNU的人并不是坏
|
||||
人,他们只是在这个问题上被严重的误导了),所以你只要给indent指定选项“-kr -i8”
|
||||
(代表“K&R,8个字符缩进”),或者使用“scripts/Lindent”,这样就可以以最时髦的方式
|
||||
缩进源代码。
|
||||
|
||||
“indent”有很多选项,特别是重新格式化注释的时候,你可能需要看一下它的手册页。不过
|
||||
记住:“indent”不能修正坏的编程习惯。
|
||||
|
||||
|
||||
第十章:Kconfig配置文件
|
||||
|
||||
对于遍布源码树的所有Kconfig*配置文件来说,它们缩进方式与C代码相比有所不同。紧挨
|
||||
在“config”定义下面的行缩进一个制表符,帮助信息则再多缩进2个空格。比如:
|
||||
|
||||
config AUDIT
|
||||
bool "Auditing support"
|
||||
depends on NET
|
||||
help
|
||||
Enable auditing infrastructure that can be used with another
|
||||
kernel subsystem, such as SELinux (which requires this for
|
||||
logging of avc messages output). Does not do system-call
|
||||
auditing without CONFIG_AUDITSYSCALL.
|
||||
|
||||
而那些危险的功能(比如某些文件系统的写支持)应该在它们的提示字符串里显著的声明这
|
||||
一点:
|
||||
|
||||
config ADFS_FS_RW
|
||||
bool "ADFS write support (DANGEROUS)"
|
||||
depends on ADFS_FS
|
||||
...
|
||||
|
||||
要查看配置文件的完整文档,请看Documentation/kbuild/kconfig-language.txt。
|
||||
|
||||
|
||||
第十一章:数据结构
|
||||
|
||||
如果一个数据结构,在创建和销毁它的单线执行环境之外可见,那么它必须要有一个引用计
|
||||
数器。内核里没有垃圾收集(并且内核之外的垃圾收集慢且效率低下),这意味着你绝对需
|
||||
要记录你对这种数据结构的使用情况。
|
||||
|
||||
引用计数意味着你能够避免上锁,并且允许多个用户并行访问这个数据结构——而不需要担心
|
||||
这个数据结构仅仅因为暂时不被使用就消失了,那些用户可能不过是沉睡了一阵或者做了一
|
||||
些其他事情而已。
|
||||
|
||||
注意上锁不能取代引用计数。上锁是为了保持数据结构的一致性,而引用计数是一个内存管
|
||||
理技巧。通常二者都需要,不要把两个搞混了。
|
||||
|
||||
很多数据结构实际上有2级引用计数,它们通常有不同“类”的用户。子类计数器统计子类用
|
||||
户的数量,每当子类计数器减至零时,全局计数器减一。
|
||||
|
||||
这种“多级引用计数”的例子可以在内存管理(“struct mm_struct”:mm_users和mm_count)
|
||||
和文件系统(“struct super_block”:s_count和s_active)中找到。
|
||||
|
||||
记住:如果另一个执行线索可以找到你的数据结构,但是这个数据结构没有引用计数器,这
|
||||
里几乎肯定是一个bug。
|
||||
|
||||
|
||||
第十二章:宏,枚举和RTL
|
||||
|
||||
用于定义常量的宏的名字及枚举里的标签需要大写。
|
||||
|
||||
#define CONSTANT 0x12345
|
||||
|
||||
在定义几个相关的常量时,最好用枚举。
|
||||
|
||||
宏的名字请用大写字母,不过形如函数的宏的名字可以用小写字母。
|
||||
|
||||
一般的,如果能写成内联函数就不要写成像函数的宏。
|
||||
|
||||
含有多个语句的宏应该被包含在一个do-while代码块里:
|
||||
|
||||
#define macrofun(a, b, c) \
|
||||
do { \
|
||||
if (a == 5) \
|
||||
do_this(b, c); \
|
||||
} while (0)
|
||||
|
||||
使用宏的时候应避免的事情:
|
||||
|
||||
1) 影响控制流程的宏:
|
||||
|
||||
#define FOO(x) \
|
||||
do { \
|
||||
if (blah(x) < 0) \
|
||||
return -EBUGGERED; \
|
||||
} while(0)
|
||||
|
||||
非常不好。它看起来像一个函数,不过却能导致“调用”它的函数退出;不要打乱读者大脑里
|
||||
的语法分析器。
|
||||
|
||||
2) 依赖于一个固定名字的本地变量的宏:
|
||||
|
||||
#define FOO(val) bar(index, val)
|
||||
|
||||
可能看起来像是个不错的东西,不过它非常容易把读代码的人搞糊涂,而且容易导致看起来
|
||||
不相关的改动带来错误。
|
||||
|
||||
3) 作为左值的带参数的宏: FOO(x) = y;如果有人把FOO变成一个内联函数的话,这种用
|
||||
法就会出错了。
|
||||
|
||||
4) 忘记了优先级:使用表达式定义常量的宏必须将表达式置于一对小括号之内。带参数的
|
||||
宏也要注意此类问题。
|
||||
|
||||
#define CONSTANT 0x4000
|
||||
#define CONSTEXP (CONSTANT | 3)
|
||||
|
||||
cpp手册对宏的讲解很详细。Gcc internals手册也详细讲解了RTL(译注:register
|
||||
transfer language),内核里的汇编语言经常用到它。
|
||||
|
||||
|
||||
第十三章:打印内核消息
|
||||
|
||||
内核开发者应该是受过良好教育的。请一定注意内核信息的拼写,以给人以好的印象。不要
|
||||
用不规范的单词比如“dont”,而要用“do not”或者“don't”。保证这些信息简单、明了、无
|
||||
歧义。
|
||||
|
||||
内核信息不必以句号(译注:英文句号,即点)结束。
|
||||
|
||||
在小括号里打印数字(%d)没有任何价值,应该避免这样做。
|
||||
|
||||
<linux/device.h>里有一些驱动模型诊断宏,你应该使用它们,以确保信息对应于正确的
|
||||
设备和驱动,并且被标记了正确的消息级别。这些宏有:dev_err(), dev_warn(),
|
||||
dev_info()等等。对于那些不和某个特定设备相关连的信息,<linux/kernel.h>定义了
|
||||
pr_debug()和pr_info()。
|
||||
|
||||
写出好的调试信息可以是一个很大的挑战;当你写出来之后,这些信息在远程除错的时候
|
||||
就会成为极大的帮助。当DEBUG符号没有被定义的时候,这些信息不应该被编译进内核里
|
||||
(也就是说,默认地,它们不应该被包含在内)。如果你使用dev_dbg()或者pr_debug(),
|
||||
就能自动达到这个效果。很多子系统拥有Kconfig选项来启用-DDEBUG。还有一个相关的惯例
|
||||
是使用VERBOSE_DEBUG来添加dev_vdbg()消息到那些已经由DEBUG启用的消息之上。
|
||||
|
||||
|
||||
第十四章:分配内存
|
||||
|
||||
内核提供了下面的一般用途的内存分配函数:kmalloc(),kzalloc(),kcalloc()和
|
||||
vmalloc()。请参考API文档以获取有关它们的详细信息。
|
||||
|
||||
传递结构体大小的首选形式是这样的:
|
||||
|
||||
p = kmalloc(sizeof(*p), ...);
|
||||
|
||||
另外一种传递方式中,sizeof的操作数是结构体的名字,这样会降低可读性,并且可能会引
|
||||
入bug。有可能指针变量类型被改变时,而对应的传递给内存分配函数的sizeof的结果不变。
|
||||
|
||||
强制转换一个void指针返回值是多余的。C语言本身保证了从void指针到其他任何指针类型
|
||||
的转换是没有问题的。
|
||||
|
||||
|
||||
第十五章:内联弊病
|
||||
|
||||
有一个常见的误解是内联函数是gcc提供的可以让代码运行更快的一个选项。虽然使用内联
|
||||
函数有时候是恰当的(比如作为一种替代宏的方式,请看第十二章),不过很多情况下不是
|
||||
这样。inline关键字的过度使用会使内核变大,从而使整个系统运行速度变慢。因为大内核
|
||||
会占用更多的指令高速缓存(译注:一级缓存通常是指令缓存和数据缓存分开的)而且会导
|
||||
致pagecache的可用内存减少。想象一下,一次pagecache未命中就会导致一次磁盘寻址,将
|
||||
耗时5毫秒。5毫秒的时间内CPU能执行很多很多指令。
|
||||
|
||||
一个基本的原则是如果一个函数有3行以上,就不要把它变成内联函数。这个原则的一个例
|
||||
外是,如果你知道某个参数是一个编译时常量,而且因为这个常量你确定编译器在编译时能
|
||||
优化掉你的函数的大部分代码,那仍然可以给它加上inline关键字。kmalloc()内联函数就
|
||||
是一个很好的例子。
|
||||
|
||||
人们经常主张给static的而且只用了一次的函数加上inline,如此不会有任何损失,因为没
|
||||
有什么好权衡的。虽然从技术上说这是正确的,但是实际上这种情况下即使不加inline gcc
|
||||
也可以自动使其内联。而且其他用户可能会要求移除inline,由此而来的争论会抵消inline
|
||||
自身的潜在价值,得不偿失。
|
||||
|
||||
|
||||
第十六章:函数返回值及命名
|
||||
|
||||
函数可以返回很多种不同类型的值,最常见的一种是表明函数执行成功或者失败的值。这样
|
||||
的一个值可以表示为一个错误代码整数(-Exxx=失败,0=成功)或者一个“成功”布尔值(
|
||||
0=失败,非0=成功)。
|
||||
|
||||
混合使用这两种表达方式是难于发现的bug的来源。如果C语言本身严格区分整形和布尔型变
|
||||
量,那么编译器就能够帮我们发现这些错误……不过C语言不区分。为了避免产生这种bug,请
|
||||
遵循下面的惯例:
|
||||
|
||||
如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代码整
|
||||
数。如果是一个判断,那么函数应该返回一个“成功”布尔值。
|
||||
|
||||
比如,“add work”是一个命令,所以add_work()函数在成功时返回0,在失败时返回-EBUSY。
|
||||
类似的,因为“PCI device present”是一个判断,所以pci_dev_present()函数在成功找到
|
||||
一个匹配的设备时应该返回1,如果找不到时应该返回0。
|
||||
|
||||
所有导出(译注:EXPORT)的函数都必须遵守这个惯例,所有的公共函数也都应该如此。私
|
||||
有(static)函数不需要如此,但是我们也推荐这样做。
|
||||
|
||||
返回值是实际计算结果而不是计算是否成功的标志的函数不受此惯例的限制。一般的,他们
|
||||
通过返回一些正常值范围之外的结果来表示出错。典型的例子是返回指针的函数,他们使用
|
||||
NULL或者ERR_PTR机制来报告错误。
|
||||
|
||||
|
||||
第十七章:不要重新发明内核宏
|
||||
|
||||
头文件include/linux/kernel.h包含了一些宏,你应该使用它们,而不要自己写一些它们的
|
||||
变种。比如,如果你需要计算一个数组的长度,使用这个宏
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
类似的,如果你要计算某结构体成员的大小,使用
|
||||
|
||||
#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
|
||||
|
||||
还有可以做严格的类型检查的min()和max()宏,如果你需要可以使用它们。你可以自己看看
|
||||
那个头文件里还定义了什么你可以拿来用的东西,如果有定义的话,你就不应在你的代码里
|
||||
自己重新定义。
|
||||
|
||||
|
||||
第十八章:编辑器模式行和其他需要罗嗦的事情
|
||||
|
||||
有一些编辑器可以解释嵌入在源文件里的由一些特殊标记标明的配置信息。比如,emacs
|
||||
能够解释被标记成这样的行:
|
||||
|
||||
-*- mode: c -*-
|
||||
|
||||
或者这样的:
|
||||
|
||||
/*
|
||||
Local Variables:
|
||||
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
|
||||
End:
|
||||
*/
|
||||
|
||||
Vim能够解释这样的标记:
|
||||
|
||||
/* vim:set sw=8 noet */
|
||||
|
||||
不要在源代码中包含任何这样的内容。每个人都有他自己的编辑器配置,你的源文件不应
|
||||
该覆盖别人的配置。这包括有关缩进和模式配置的标记。人们可以使用他们自己定制的模
|
||||
式,或者使用其他可以产生正确的缩进的巧妙方法。
|
||||
|
||||
|
||||
|
||||
附录 I:参考
|
||||
|
||||
The C Programming Language, 第二版, 作者Brian W. Kernighan和Denni
|
||||
M. Ritchie. Prentice Hall, Inc., 1988. ISBN 0-13-110362-8 (软皮),
|
||||
0-13-110370-9 (硬皮). URL: http://cm.bell-labs.com/cm/cs/cbook/
|
||||
|
||||
The Practice of Programming 作者Brian W. Kernighan和Rob Pike. Addison-Wesley,
|
||||
Inc., 1999. ISBN 0-201-61586-X. URL: http://cm.bell-labs.com/cm/cs/tpop/
|
||||
|
||||
cpp,gcc,gcc internals和indent的GNU手册——和K&R及本文相符合的部分,全部可以在
|
||||
http://www.gnu.org/manual/找到
|
||||
|
||||
WG14是C语言的国际标准化工作组,URL: http://www.open-std.org/JTC1/SC22/WG14/
|
||||
|
||||
Kernel CodingStyle,作者greg@kroah.com发表于OLS 2002:
|
||||
http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/
|
||||
|
||||
--
|
||||
最后更新于2007年7月13日。
|
538
Documentation/zh_CN/HOWTO
Normal file
538
Documentation/zh_CN/HOWTO
Normal file
|
@ -0,0 +1,538 @@
|
|||
Chinese translated version of Documentation/HOWTO
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Greg Kroah-Hartman <greg@kroah.com>
|
||||
Chinese maintainer: Li Yang <leoli@freescale.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/HOWTO 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Greg Kroah-Hartman <greg@kroah.com>
|
||||
中文版维护者: 李阳 Li Yang <leoli@freescale.com>
|
||||
中文版翻译者: 李阳 Li Yang <leoli@freescale.com>
|
||||
中文版校译者: 钟宇 TripleX Chung <xxx.phy@gmail.com>
|
||||
陈琦 Maggie Chen <chenqi@beyondsoft.com>
|
||||
王聪 Wang Cong <xiyou.wangcong@gmail.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
如何参与Linux内核开发
|
||||
---------------------
|
||||
|
||||
这是一篇将如何参与Linux内核开发的相关问题一网打尽的终极秘笈。它将指导你
|
||||
成为一名Linux内核开发者,并且学会如何同Linux内核开发社区合作。它尽可能不
|
||||
包括任何关于内核编程的技术细节,但会给你指引一条获得这些知识的正确途径。
|
||||
|
||||
如果这篇文章中的任何内容不再适用,请给文末列出的文件维护者发送补丁。
|
||||
|
||||
|
||||
入门
|
||||
----
|
||||
|
||||
你想了解如何成为一名Linux内核开发者?或者老板吩咐你“给这个设备写个Linux
|
||||
驱动程序”?这篇文章的目的就是教会你达成这些目标的全部诀窍,它将描述你需
|
||||
要经过的流程以及给出如何同内核社区合作的一些提示。它还将试图解释内核社区
|
||||
为何这样运作。
|
||||
|
||||
Linux内核大部分是由C语言写成的,一些体系结构相关的代码用到了汇编语言。要
|
||||
参与内核开发,你必须精通C语言。除非你想为某个架构开发底层代码,否则你并
|
||||
不需要了解(任何体系结构的)汇编语言。下面列举的书籍虽然不能替代扎实的C
|
||||
语言教育和多年的开发经验,但如果需要的话,做为参考还是不错的:
|
||||
- "The C Programming Language" by Kernighan and Ritchie [Prentice Hall]
|
||||
《C程序设计语言(第2版·新版)》(徐宝文 李志 译)[机械工业出版社]
|
||||
- "Practical C Programming" by Steve Oualline [O'Reilly]
|
||||
《实用C语言编程(第三版)》(郭大海 译)[中国电力出版社]
|
||||
- "C: A Reference Manual" by Harbison and Steele [Prentice Hall]
|
||||
《C语言参考手册(原书第5版)》(邱仲潘 等译)[机械工业出版社]
|
||||
|
||||
Linux内核使用GNU C和GNU工具链开发。虽然它遵循ISO C89标准,但也用到了一些
|
||||
标准中没有定义的扩展。内核是自给自足的C环境,不依赖于标准C库的支持,所以
|
||||
并不支持C标准中的部分定义。比如long long类型的大数除法和浮点运算就不允许
|
||||
使用。有时候确实很难弄清楚内核对工具链的要求和它所使用的扩展,不幸的是目
|
||||
前还没有明确的参考资料可以解释它们。请查阅gcc信息页(使用“info gcc”命令
|
||||
显示)获得一些这方面信息。
|
||||
|
||||
请记住你是在学习怎么和已经存在的开发社区打交道。它由一群形形色色的人组成,
|
||||
他们对代码、风格和过程有着很高的标准。这些标准是在长期实践中总结出来的,
|
||||
适应于地理上分散的大型开发团队。它们已经被很好得整理成档,建议你在开发
|
||||
之前尽可能多的学习这些标准,而不要期望别人来适应你或者你公司的行为方式。
|
||||
|
||||
|
||||
法律问题
|
||||
--------
|
||||
|
||||
Linux内核源代码都是在GPL(通用公共许可证)的保护下发布的。要了解这种许可
|
||||
的细节请查看源代码主目录下的COPYING文件。如果你对它还有更深入问题请联系
|
||||
律师,而不要在Linux内核邮件组上提问。因为邮件组里的人并不是律师,不要期
|
||||
望他们的话有法律效力。
|
||||
|
||||
对于GPL的常见问题和解答,请访问以下链接:
|
||||
http://www.gnu.org/licenses/gpl-faq.html
|
||||
|
||||
|
||||
文档
|
||||
----
|
||||
|
||||
Linux内核代码中包含有大量的文档。这些文档对于学习如何与内核社区互动有着
|
||||
不可估量的价值。当一个新的功能被加入内核,最好把解释如何使用这个功能的文
|
||||
档也放进内核。当内核的改动导致面向用户空间的接口发生变化时,最好将相关信
|
||||
息或手册页(manpages)的补丁发到mtk.manpages@gmail.com,以向手册页(manpages)
|
||||
的维护者解释这些变化。
|
||||
|
||||
以下是内核代码中需要阅读的文档:
|
||||
README
|
||||
文件简要介绍了Linux内核的背景,并且描述了如何配置和编译内核。内核的
|
||||
新用户应该从这里开始。
|
||||
|
||||
Documentation/Changes
|
||||
文件给出了用来编译和使用内核所需要的最小软件包列表。
|
||||
|
||||
Documentation/CodingStyle
|
||||
描述Linux内核的代码风格和理由。所有新代码需要遵守这篇文档中定义的规
|
||||
范。大多数维护者只会接收符合规定的补丁,很多人也只会帮忙检查符合风格
|
||||
的代码。
|
||||
|
||||
Documentation/SubmittingPatches
|
||||
Documentation/SubmittingDrivers
|
||||
这两份文档明确描述如何创建和发送补丁,其中包括(但不仅限于):
|
||||
- 邮件内容
|
||||
- 邮件格式
|
||||
- 选择收件人
|
||||
遵守这些规定并不能保证提交成功(因为所有补丁需要通过严格的内容和风格
|
||||
审查),但是忽视他们几乎就意味着失败。
|
||||
|
||||
其他关于如何正确地生成补丁的优秀文档包括:
|
||||
"The Perfect Patch"
|
||||
http://www.ozlabs.org/~akpm/stuff/tpp.txt
|
||||
"Linux kernel patch submission format"
|
||||
http://linux.yyz.us/patch-format.html
|
||||
|
||||
Documentation/stable_api_nonsense.txt
|
||||
论证内核为什么特意不包括稳定的内核内部API,也就是说不包括像这样的特
|
||||
性:
|
||||
- 子系统中间层(为了兼容性?)
|
||||
- 在不同操作系统间易于移植的驱动程序
|
||||
- 减缓(甚至阻止)内核代码的快速变化
|
||||
这篇文档对于理解Linux的开发哲学至关重要。对于将开发平台从其他操作系
|
||||
统转移到Linux的人来说也很重要。
|
||||
|
||||
Documentation/SecurityBugs
|
||||
如果你认为自己发现了Linux内核的安全性问题,请根据这篇文档中的步骤来
|
||||
提醒其他内核开发者并帮助解决这个问题。
|
||||
|
||||
Documentation/ManagementStyle
|
||||
描述内核维护者的工作方法及其共有特点。这对于刚刚接触内核开发(或者对
|
||||
它感到好奇)的人来说很重要,因为它解释了很多对于内核维护者独特行为的
|
||||
普遍误解与迷惑。
|
||||
|
||||
Documentation/stable_kernel_rules.txt
|
||||
解释了稳定版内核发布的规则,以及如何将改动放入这些版本的步骤。
|
||||
|
||||
Documentation/kernel-docs.txt
|
||||
有助于内核开发的外部文档列表。如果你在内核自带的文档中没有找到你想找
|
||||
的内容,可以查看这些文档。
|
||||
|
||||
Documentation/applying-patches.txt
|
||||
关于补丁是什么以及如何将它打在不同内核开发分支上的好介绍
|
||||
|
||||
内核还拥有大量从代码自动生成的文档。它包含内核内部API的全面介绍以及如何
|
||||
妥善处理加锁的规则。生成的文档会放在 Documentation/DocBook/目录下。在内
|
||||
核源码的主目录中使用以下不同命令将会分别生成PDF、Postscript、HTML和手册
|
||||
页等不同格式的文档:
|
||||
make pdfdocs
|
||||
make psdocs
|
||||
make htmldocs
|
||||
make mandocs
|
||||
|
||||
|
||||
如何成为内核开发者
|
||||
------------------
|
||||
如果你对Linux内核开发一无所知,你应该访问“Linux内核新手”计划:
|
||||
http://kernelnewbies.org
|
||||
它拥有一个可以问各种最基本的内核开发问题的邮件列表(在提问之前一定要记得
|
||||
查找已往的邮件,确认是否有人已经回答过相同的问题)。它还拥有一个可以获得
|
||||
实时反馈的IRC聊天频道,以及大量对于学习Linux内核开发相当有帮助的文档。
|
||||
|
||||
网站简要介绍了源代码组织结构、子系统划分以及目前正在进行的项目(包括内核
|
||||
中的和单独维护的)。它还提供了一些基本的帮助信息,比如如何编译内核和打补
|
||||
丁。
|
||||
|
||||
如果你想加入内核开发社区并协助完成一些任务,却找不到从哪里开始,可以访问
|
||||
“Linux内核房管员”计划:
|
||||
http://kernelnewbies.org/KernelJanitors
|
||||
这是极佳的起点。它提供一个相对简单的任务列表,列出内核代码中需要被重新
|
||||
整理或者改正的地方。通过和负责这个计划的开发者们一同工作,你会学到将补丁
|
||||
集成进内核的基本原理。如果还没有决定下一步要做什么的话,你还可能会得到方
|
||||
向性的指点。
|
||||
|
||||
如果你已经有一些现成的代码想要放到内核中,但是需要一些帮助来使它们拥有正
|
||||
确的格式。请访问“内核导师”计划。这个计划就是用来帮助你完成这个目标的。它
|
||||
是一个邮件列表,地址如下:
|
||||
http://selenic.com/mailman/listinfo/kernel-mentors
|
||||
|
||||
在真正动手修改内核代码之前,理解要修改的代码如何运作是必需的。要达到这个
|
||||
目的,没什么办法比直接读代码更有效了(大多数花招都会有相应的注释),而且
|
||||
一些特制的工具还可以提供帮助。例如,“Linux代码交叉引用”项目就是一个值得
|
||||
特别推荐的帮助工具,它将源代码显示在有编目和索引的网页上。其中一个更新及
|
||||
时的内核源码库,可以通过以下地址访问:
|
||||
http://sosdg.org/~coywolf/lxr/
|
||||
|
||||
|
||||
开发流程
|
||||
--------
|
||||
|
||||
目前Linux内核开发流程包括几个“主内核分支”和很多子系统相关的内核分支。这
|
||||
些分支包括:
|
||||
- 2.6.x主内核源码树
|
||||
- 2.6.x.y -stable内核源码树
|
||||
- 2.6.x -git内核补丁集
|
||||
- 2.6.x -mm内核补丁集
|
||||
- 子系统相关的内核源码树和补丁集
|
||||
|
||||
|
||||
2.6.x内核主源码树
|
||||
-----------------
|
||||
2.6.x内核是由Linus Torvalds(Linux的创造者)亲自维护的。你可以在
|
||||
kernel.org网站的pub/linux/kernel/v2.6/目录下找到它。它的开发遵循以下步
|
||||
骤:
|
||||
- 每当一个新版本的内核被发布,为期两周的集成窗口将被打开。在这段时间里
|
||||
维护者可以向Linus提交大段的修改,通常这些修改已经被放到-mm内核中几个
|
||||
星期了。提交大量修改的首选方式是使用git工具(内核的代码版本管理工具
|
||||
,更多的信息可以在http://git.or.cz/获取),不过使用普通补丁也是可以
|
||||
的。
|
||||
- 两个星期以后-rc1版本内核发布。之后只有不包含可能影响整个内核稳定性的
|
||||
新功能的补丁才可能被接受。请注意一个全新的驱动程序(或者文件系统)有
|
||||
可能在-rc1后被接受是因为这样的修改完全独立,不会影响其他的代码,所以
|
||||
没有造成内核退步的风险。在-rc1以后也可以用git向Linus提交补丁,不过所
|
||||
有的补丁需要同时被发送到相应的公众邮件列表以征询意见。
|
||||
- 当Linus认为当前的git源码树已经达到一个合理健全的状态足以发布供人测试
|
||||
时,一个新的-rc版本就会被发布。计划是每周都发布新的-rc版本。
|
||||
- 这个过程一直持续下去直到内核被认为达到足够稳定的状态,持续时间大概是
|
||||
6个星期。
|
||||
- 以下地址跟踪了在每个-rc发布中发现的退步列表:
|
||||
http://kernelnewbies.org/known_regressions
|
||||
|
||||
关于内核发布,值得一提的是Andrew Morton在linux-kernel邮件列表中如是说:
|
||||
“没有人知道新内核何时会被发布,因为发布是根据已知bug的情况来决定
|
||||
的,而不是根据一个事先制定好的时间表。”
|
||||
|
||||
|
||||
2.6.x.y -stable(稳定版)内核源码树
|
||||
-----------------------------------
|
||||
由4个数字组成的内核版本号说明此内核是-stable版本。它们包含基于2.6.x版本
|
||||
内核的相对较小且至关重要的修补,这些修补针对安全性问题或者严重的内核退步。
|
||||
|
||||
这种版本的内核适用于那些期望获得最新的稳定版内核并且不想参与测试开发版或
|
||||
者实验版的用户。
|
||||
|
||||
如果没有2.6.x.y版本内核存在,那么最新的2.6.x版本内核就相当于是当前的稳定
|
||||
版内核。
|
||||
|
||||
2.6.x.y版本由“稳定版”小组(邮件地址<stable@vger.kernel.org>)维护,一般隔周发
|
||||
布新版本。
|
||||
|
||||
内核源码中的Documentation/stable_kernel_rules.txt文件具体描述了可被稳定
|
||||
版内核接受的修改类型以及发布的流程。
|
||||
|
||||
|
||||
2.6.x -git补丁集
|
||||
----------------
|
||||
Linus的内核源码树的每日快照,这个源码树是由git工具管理的(由此得名)。这
|
||||
些补丁通常每天更新以反映Linus的源码树的最新状态。它们比-rc版本的内核源码
|
||||
树更具试验性质,因为这个补丁集是全自动生成的,没有任何人来确认其是否真正
|
||||
健全。
|
||||
|
||||
|
||||
2.6.x -mm补丁集
|
||||
---------------
|
||||
这是由Andrew Morton维护的试验性内核补丁集。Andrew将所有子系统的内核源码
|
||||
和补丁拼凑到一起,并且加入了大量从linux-kernel邮件列表中采集的补丁。这个
|
||||
源码树是新功能和补丁的试炼场。当补丁在-mm补丁集里证明了其价值以后Andrew
|
||||
或者相应子系统的维护者会将补丁发给Linus以便集成进主内核源码树。
|
||||
|
||||
在将所有新补丁发给Linus以集成到主内核源码树之前,我们非常鼓励先把这些补
|
||||
丁放在-mm版内核源码树中进行测试。
|
||||
|
||||
这些内核版本不适合在需要稳定运行的系统上运行,因为运行它们比运行任何其他
|
||||
内核分支都更具有风险。
|
||||
|
||||
如果你想为内核开发进程提供帮助,请尝试并使用这些内核版本,并在
|
||||
linux-kernel邮件列表中提供反馈,告诉大家你遇到了问题还是一切正常。
|
||||
|
||||
通常-mm版补丁集不光包括这些额外的试验性补丁,还包括发布时-git版主源码树
|
||||
中的改动。
|
||||
|
||||
-mm版内核没有固定的发布周期,但是通常在每两个-rc版内核发布之间都会有若干
|
||||
个-mm版内核发布(一般是1至3个)。
|
||||
|
||||
|
||||
子系统相关内核源码树和补丁集
|
||||
----------------------------
|
||||
相当一部分内核子系统开发者会公开他们自己的开发源码树,以便其他人能了解内
|
||||
核的不同领域正在发生的事情。如上所述,这些源码树会被集成到-mm版本内核中。
|
||||
|
||||
下面是目前可用的一些内核源码树的列表:
|
||||
通过git管理的源码树:
|
||||
- Kbuild开发源码树, Sam Ravnborg <sam@ravnborg.org>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/sam/kbuild.git
|
||||
|
||||
- ACPI开发源码树, Len Brown <len.brown@intel.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6.git
|
||||
|
||||
- 块设备开发源码树, Jens Axboe <axboe@suse.de>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/axboe/linux-2.6-block.git
|
||||
|
||||
- DRM开发源码树, Dave Airlie <airlied@linux.ie>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/airlied/drm-2.6.git
|
||||
|
||||
- ia64开发源码树, Tony Luck <tony.luck@intel.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
|
||||
|
||||
- ieee1394开发源码树, Jody McIntyre <scjody@modernduck.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/scjody/ieee1394.git
|
||||
|
||||
- infiniband开发源码树, Roland Dreier <rolandd@cisco.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/roland/infiniband.git
|
||||
|
||||
- libata开发源码树, Jeff Garzik <jgarzik@pobox.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev.git
|
||||
|
||||
- 网络驱动程序开发源码树, Jeff Garzik <jgarzik@pobox.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/jgarzik/netdev-2.6.git
|
||||
|
||||
- pcmcia开发源码树, Dominik Brodowski <linux@dominikbrodowski.net>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/brodo/pcmcia-2.6.git
|
||||
|
||||
- SCSI开发源码树, James Bottomley <James.Bottomley@SteelEye.com>
|
||||
git.kernel.org:/pub/scm/linux/kernel/git/jejb/scsi-misc-2.6.git
|
||||
|
||||
使用quilt管理的补丁集:
|
||||
- USB, PCI, 驱动程序核心和I2C, Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
kernel.org/pub/linux/kernel/people/gregkh/gregkh-2.6/
|
||||
- x86-64, 部分i386, Andi Kleen <ak@suse.de>
|
||||
ftp.firstfloor.org:/pub/ak/x86_64/quilt/
|
||||
|
||||
其他内核源码树可以在http://git.kernel.org的列表中和MAINTAINERS文件里
|
||||
找到。
|
||||
|
||||
报告bug
|
||||
-------
|
||||
|
||||
bugzilla.kernel.org是Linux内核开发者们用来跟踪内核Bug的网站。我们鼓励用
|
||||
户在这个工具中报告找到的所有bug。如何使用内核bugzilla的细节请访问:
|
||||
http://test.kernel.org/bugzilla/faq.html
|
||||
|
||||
内核源码主目录中的REPORTING-BUGS文件里有一个很好的模板。它指导用户如何报
|
||||
告可能的内核bug以及需要提供哪些信息来帮助内核开发者们找到问题的根源。
|
||||
|
||||
|
||||
利用bug报告
|
||||
-----------
|
||||
|
||||
练习内核开发技能的最好办法就是修改其他人报告的bug。你不光可以帮助内核变
|
||||
得更加稳定,还可以学会如何解决实际问题从而提高自己的技能,并且让其他开发
|
||||
者感受到你的存在。修改bug是赢得其他开发者赞誉的最好办法,因为并不是很多
|
||||
人都喜欢浪费时间去修改别人报告的bug。
|
||||
|
||||
要尝试修改已知的bug,请访问http://bugzilla.kernel.org网址。如果你想获得
|
||||
最新bug的通知,可以订阅bugme-new邮件列表(只有新的bug报告会被寄到这里)
|
||||
或者订阅bugme-janitor邮件列表(所有bugzilla的变动都会被寄到这里)。
|
||||
|
||||
https://lists.linux-foundation.org/mailman/listinfo/bugme-new
|
||||
https://lists.linux-foundation.org/mailman/listinfo/bugme-janitors
|
||||
|
||||
|
||||
邮件列表
|
||||
--------
|
||||
|
||||
正如上面的文档所描述,大多数的骨干内核开发者都加入了Linux Kernel邮件列
|
||||
表。如何订阅和退订列表的细节可以在这里找到:
|
||||
http://vger.kernel.org/vger-lists.html#linux-kernel
|
||||
网上很多地方都有这个邮件列表的存档(archive)。可以使用搜索引擎来找到这些
|
||||
存档。比如:
|
||||
http://dir.gmane.org/gmane.linux.kernel
|
||||
在发信之前,我们强烈建议你先在存档中搜索你想要讨论的问题。很多已经被详细
|
||||
讨论过的问题只在邮件列表的存档中可以找到。
|
||||
|
||||
大多数内核子系统也有自己独立的邮件列表来协调各自的开发工作。从
|
||||
MAINTAINERS文件中可以找到不同话题对应的邮件列表。
|
||||
|
||||
很多邮件列表架设在kernel.org服务器上。这些列表的信息可以在这里找到:
|
||||
http://vger.kernel.org/vger-lists.html
|
||||
|
||||
在使用这些邮件列表时,请记住保持良好的行为习惯。下面的链接提供了与这些列
|
||||
表(或任何其它邮件列表)交流的一些简单规则,虽然内容有点滥竽充数。
|
||||
http://www.albion.com/netiquette/
|
||||
|
||||
当有很多人回复你的邮件时,邮件的抄送列表会变得很长。请不要将任何人从抄送
|
||||
列表中删除,除非你有足够的理由这么做。也不要只回复到邮件列表。请习惯于同
|
||||
一封邮件接收两次(一封来自发送者一封来自邮件列表),而不要试图通过添加一
|
||||
些奇特的邮件头来解决这个问题,人们不会喜欢的。
|
||||
|
||||
记住保留你所回复内容的上下文和源头。在你回复邮件的顶部保留“某某某说到……”
|
||||
这几行。将你的评论加在被引用的段落之间而不要放在邮件的顶部。
|
||||
|
||||
如果你在邮件中附带补丁,请确认它们是可以直接阅读的纯文本(如
|
||||
Documentation/SubmittingPatches文档中所述)。内核开发者们不希望遇到附件
|
||||
或者被压缩了的补丁。只有这样才能保证他们可以直接评论你的每行代码。请确保
|
||||
你使用的邮件发送程序不会修改空格和制表符。一个防范性的测试方法是先将邮件
|
||||
发送给自己,然后自己尝试是否可以顺利地打上收到的补丁。如果测试不成功,请
|
||||
调整或者更换你的邮件发送程序直到它正确工作为止。
|
||||
|
||||
总而言之,请尊重其他的邮件列表订阅者。
|
||||
|
||||
|
||||
同内核社区合作
|
||||
----------------
|
||||
|
||||
内核社区的目标就是提供尽善尽美的内核。所以当你提交补丁期望被接受进内核的
|
||||
时候,它的技术价值以及其他方面都将被评审。那么你可能会得到什么呢?
|
||||
- 批评
|
||||
- 评论
|
||||
- 要求修改
|
||||
- 要求证明修改的必要性
|
||||
- 沉默
|
||||
|
||||
要记住,这些是把补丁放进内核的正常情况。你必须学会听取对补丁的批评和评论,
|
||||
从技术层面评估它们,然后要么重写你的补丁要么简明扼要地论证修改是不必要
|
||||
的。如果你发的邮件没有得到任何回应,请过几天后再试一次,因为有时信件会湮
|
||||
没在茫茫信海中。
|
||||
|
||||
你不应该做的事情:
|
||||
- 期望自己的补丁不受任何质疑就直接被接受
|
||||
- 翻脸
|
||||
- 忽略别人的评论
|
||||
- 没有按照别人的要求做任何修改就重新提交
|
||||
|
||||
在一个努力追寻最好技术方案的社区里,对于一个补丁有多少好处总会有不同的见
|
||||
解。你必须要抱着合作的态度,愿意改变自己的观点来适应内核的风格。或者至少
|
||||
愿意去证明你的想法是有价值的。记住,犯错误是允许的,只要你愿意朝着正确的
|
||||
方案去努力。
|
||||
|
||||
如果你的第一个补丁换来的是一堆修改建议,这是很正常的。这并不代表你的补丁
|
||||
不会被接受,也不意味着有人和你作对。你只需要改正所有提出的问题然后重新发
|
||||
送你的补丁。
|
||||
|
||||
内核社区和公司文化的差异
|
||||
------------------------
|
||||
|
||||
内核社区的工作模式同大多数传统公司开发队伍的工作模式并不相同。下面这些例
|
||||
子,可以帮助你避免某些可能发生问题:
|
||||
用这些话介绍你的修改提案会有好处:
|
||||
- 它同时解决了多个问题
|
||||
- 它删除了2000行代码
|
||||
- 这是补丁,它已经解释了我想要说明的
|
||||
- 我在5种不同的体系结构上测试过它……
|
||||
- 这是一系列小补丁用来……
|
||||
- 这个修改提高了普通机器的性能……
|
||||
|
||||
应该避免如下的说法:
|
||||
- 我们在AIX/ptx/Solaris就是这么做的,所以这么做肯定是好的……
|
||||
- 我做这行已经20年了,所以……
|
||||
- 为了我们公司赚钱考虑必须这么做
|
||||
- 这是我们的企业产品线所需要的
|
||||
- 这里是描述我观点的1000页设计文档
|
||||
- 这是一个5000行的补丁用来……
|
||||
- 我重写了现在乱七八糟的代码,这就是……
|
||||
- 我被规定了最后期限,所以这个补丁需要立刻被接受
|
||||
|
||||
另外一个内核社区与大部分传统公司的软件开发队伍不同的地方是无法面对面地交
|
||||
流。使用电子邮件和IRC聊天工具做为主要沟通工具的一个好处是性别和种族歧视
|
||||
将会更少。Linux内核的工作环境更能接受妇女和少数族群,因为每个人在别人眼
|
||||
里只是一个邮件地址。国际化也帮助了公平的实现,因为你无法通过姓名来判断人
|
||||
的性别。男人有可能叫李丽,女人也有可能叫王刚。大多数在Linux内核上工作过
|
||||
并表达过看法的女性对在linux上工作的经历都给出了正面的评价。
|
||||
|
||||
对于一些不习惯使用英语的人来说,语言可能是一个引起问题的障碍。在邮件列表
|
||||
中要正确地表达想法必需良好地掌握语言,所以建议你在发送邮件之前最好检查一
|
||||
下英文写得是否正确。
|
||||
|
||||
|
||||
拆分修改
|
||||
--------
|
||||
|
||||
Linux内核社区并不喜欢一下接收大段的代码。修改需要被恰当地介绍、讨论并且
|
||||
拆分成独立的小段。这几乎完全和公司中的习惯背道而驰。你的想法应该在开发最
|
||||
开始的阶段就让大家知道,这样你就可以及时获得对你正在进行的开发的反馈。这
|
||||
样也会让社区觉得你是在和他们协作,而不是仅仅把他们当作倾销新功能的对象。
|
||||
无论如何,你不要一次性地向邮件列表发送50封信,你的补丁序列应该永远用不到
|
||||
这么多。
|
||||
|
||||
将补丁拆开的原因如下:
|
||||
|
||||
1) 小的补丁更有可能被接受,因为它们不需要太多的时间和精力去验证其正确性。
|
||||
一个5行的补丁,可能在维护者看了一眼以后就会被接受。而500行的补丁则
|
||||
需要数个小时来审查其正确性(所需时间随补丁大小增加大约呈指数级增长)。
|
||||
|
||||
当出了问题的时候,小的补丁也会让调试变得非常容易。一个一个补丁地回溯
|
||||
将会比仔细剖析一个被打上的大补丁(这个补丁破坏了其他东西)容易得多。
|
||||
|
||||
2)不光发送小的补丁很重要,在提交之前重新编排、化简(或者仅仅重新排列)
|
||||
补丁也是很重要的。
|
||||
|
||||
这里有内核开发者Al Viro打的一个比方:
|
||||
“想象一个老师正在给学生批改数学作业。老师并不希望看到学生为了得
|
||||
到正确解法所进行的尝试和产生的错误。他希望看到的是最干净最优雅的
|
||||
解答。好学生了解这点,绝不会把最终解决之前的中间方案提交上去。”
|
||||
|
||||
内核开发也是这样。维护者和评审者不希望看到一个人在解决问题时的思
|
||||
考过程。他们只希望看到简单和优雅的解决方案。
|
||||
|
||||
直接给出一流的解决方案,和社区一起协作讨论尚未完成的工作,这两者之间似乎
|
||||
很难找到一个平衡点。所以最好尽早开始收集有利于你进行改进的反馈;同时也要
|
||||
保证修改分成很多小块,这样在整个项目都准备好被包含进内核之前,其中的一部
|
||||
分可能会先被接收。
|
||||
|
||||
必须了解这样做是不可接受的:试图将未完成的工作提交进内核,然后再找时间修
|
||||
复。
|
||||
|
||||
|
||||
证明修改的必要性
|
||||
----------------
|
||||
除了将补丁拆成小块,很重要的一点是让Linux社区了解他们为什么需要这样修改。
|
||||
你必须证明新功能是有人需要的并且是有用的。
|
||||
|
||||
|
||||
记录修改
|
||||
--------
|
||||
|
||||
当你发送补丁的时候,需要特别留意邮件正文的内容。因为这里的信息将会做为补
|
||||
丁的修改记录(ChangeLog),会被一直保留以备大家查阅。它需要完全地描述补丁,
|
||||
包括:
|
||||
- 为什么需要这个修改
|
||||
- 补丁的总体设计
|
||||
- 实现细节
|
||||
- 测试结果
|
||||
|
||||
想了解它具体应该看起来像什么,请查阅以下文档中的“ChangeLog”章节:
|
||||
“The Perfect Patch”
|
||||
http://www.ozlabs.org/~akpm/stuff/tpp.txt
|
||||
|
||||
|
||||
这些事情有时候做起来很难。要在任何方面都做到完美可能需要好几年时间。这是
|
||||
一个持续提高的过程,它需要大量的耐心和决心。只要不放弃,你一定可以做到。
|
||||
很多人已经做到了,而他们都曾经和现在的你站在同样的起点上。
|
||||
|
||||
|
||||
---------------
|
||||
感谢Paolo Ciarrocchi允许“开发流程”部分基于他所写的文章
|
||||
(http://www.kerneltravel.net/newbie/2.6-development_process),感谢Randy
|
||||
Dunlap和Gerrit Huizenga完善了应该说和不该说的列表。感谢Pat Mochel, Hanna
|
||||
Linder, Randy Dunlap, Kay Sievers, Vojtech Pavlik, Jan Kara, Josh Boyer,
|
||||
Kees Cook, Andrew Morton, Andi Kleen, Vadim Lobanov, Jesper Juhl, Adrian
|
||||
Bunk, Keri Harris, Frans Pop, David A. Wheeler, Junio Hamano, Michael
|
||||
Kerrisk和Alex Shepard的评审、建议和贡献。没有他们的帮助,这篇文档是不可
|
||||
能完成的。
|
||||
|
||||
|
||||
|
||||
英文版维护者: Greg Kroah-Hartman <greg@kroah.com>
|
39
Documentation/zh_CN/IRQ.txt
Normal file
39
Documentation/zh_CN/IRQ.txt
Normal file
|
@ -0,0 +1,39 @@
|
|||
Chinese translated version of Documentation/IRQ.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Eric W. Biederman <ebiederman@xmission.com>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/IRQ.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
英文版维护者: Eric W. Biederman <ebiederman@xmission.com>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
何为 IRQ?
|
||||
|
||||
一个 IRQ 是来自某个设备的一个中断请求。目前,它们可以来自一个硬件引脚,
|
||||
或来自一个数据包。多个设备可能连接到同个硬件引脚,从而共享一个 IRQ。
|
||||
|
||||
一个 IRQ 编号是用于告知硬件中断源的内核标识。通常情况下,这是一个
|
||||
全局 irq_desc 数组的索引,但是除了在 linux/interrupt.h 中的实现,
|
||||
具体的细节是体系结构特定的。
|
||||
|
||||
一个 IRQ 编号是设备上某个可能的中断源的枚举。通常情况下,枚举的编号是
|
||||
该引脚在系统内中断控制器的所有输入引脚中的编号。对于 ISA 总线中的情况,
|
||||
枚举的是在两个 i8259 中断控制器中 16 个输入引脚。
|
||||
|
||||
架构可以对 IRQ 编号指定额外的含义,在硬件涉及任何手工配置的情况下,
|
||||
是被提倡的。ISA 的 IRQ 是一个分配这类额外含义的典型例子。
|
50
Documentation/zh_CN/SecurityBugs
Normal file
50
Documentation/zh_CN/SecurityBugs
Normal file
|
@ -0,0 +1,50 @@
|
|||
Chinese translated version of Documentation/SecurityBugs
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: Harry Wei <harryxiyou@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/SecurityBugs 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 贾威威 Harry Wei <harryxiyou@gmail.com>
|
||||
中文版翻译者: 贾威威 Harry Wei <harryxiyou@gmail.com>
|
||||
中文版校译者: 贾威威 Harry Wei <harryxiyou@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
Linux内核开发者认为安全非常重要。因此,我们想要知道当一个有关于
|
||||
安全的漏洞被发现的时候,并且它可能会被尽快的修复或者公开。请把这个安全
|
||||
漏洞报告给Linux内核安全团队。
|
||||
|
||||
1) 联系
|
||||
|
||||
linux内核安全团队可以通过email<security@kernel.org>来联系。这是
|
||||
一组独立的安全工作人员,可以帮助改善漏洞报告并且公布和取消一个修复。安
|
||||
全团队有可能会从部分的维护者那里引进额外的帮助来了解并且修复安全漏洞。
|
||||
当遇到任何漏洞,所能提供的信息越多就越能诊断和修复。如果你不清楚什么
|
||||
是有帮助的信息,那就请重温一下REPORTING-BUGS文件中的概述过程。任
|
||||
何攻击性的代码都是非常有用的,未经报告者的同意不会被取消,除非它已经
|
||||
被公布于众。
|
||||
|
||||
2) 公开
|
||||
|
||||
Linux内核安全团队的宗旨就是和漏洞提交者一起处理漏洞的解决方案直
|
||||
到公开。我们喜欢尽快地完全公开漏洞。当一个漏洞或者修复还没有被完全地理
|
||||
解,解决方案没有通过测试或者供应商协调,可以合理地延迟公开。然而,我们
|
||||
期望这些延迟尽可能的短些,是可数的几天,而不是几个星期或者几个月。公开
|
||||
日期是通过安全团队和漏洞提供者以及供应商洽谈后的结果。公开时间表是从很
|
||||
短(特殊的,它已经被公众所知道)到几个星期。作为一个基本的默认政策,我
|
||||
们所期望通知公众的日期是7天的安排。
|
||||
|
||||
3) 保密协议
|
||||
|
||||
Linux内核安全团队不是一个正式的团体,因此不能加入任何的保密协议。
|
164
Documentation/zh_CN/SubmittingDrivers
Normal file
164
Documentation/zh_CN/SubmittingDrivers
Normal file
|
@ -0,0 +1,164 @@
|
|||
Chinese translated version of Documentation/SubmittingDrivers
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: Li Yang <leo@zh-kernel.org>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/SubmittingDrivers 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
中文版翻译者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
中文版校译者: 陈琦 Maggie Chen <chenqi@beyondsoft.com>
|
||||
王聪 Wang Cong <xiyou.wangcong@gmail.com>
|
||||
张巍 Zhang Wei <Wei.Zhang@freescale.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
如何向 Linux 内核提交驱动程序
|
||||
-----------------------------
|
||||
|
||||
这篇文档将会解释如何向不同的内核源码树提交设备驱动程序。请注意,如果你感
|
||||
兴趣的是显卡驱动程序,你也许应该访问 XFree86 项目(http://www.xfree86.org/)
|
||||
和/或 X.org 项目 (http://x.org)。
|
||||
|
||||
另请参阅 Documentation/SubmittingPatches 文档。
|
||||
|
||||
|
||||
分配设备号
|
||||
----------
|
||||
|
||||
块设备和字符设备的主设备号与从设备号是由 Linux 命名编号分配权威 LANANA(
|
||||
现在是 Torben Mathiasen)负责分配。申请的网址是 http://www.lanana.org/。
|
||||
即使不准备提交到主流内核的设备驱动也需要在这里分配设备号。有关详细信息,
|
||||
请参阅 Documentation/devices.txt。
|
||||
|
||||
如果你使用的不是已经分配的设备号,那么当你提交设备驱动的时候,它将会被强
|
||||
制分配一个新的设备号,即便这个设备号和你之前发给客户的截然不同。
|
||||
|
||||
设备驱动的提交对象
|
||||
------------------
|
||||
|
||||
Linux 2.0:
|
||||
此内核源码树不接受新的驱动程序。
|
||||
|
||||
Linux 2.2:
|
||||
此内核源码树不接受新的驱动程序。
|
||||
|
||||
Linux 2.4:
|
||||
如果所属的代码领域在内核的 MAINTAINERS 文件中列有一个总维护者,
|
||||
那么请将驱动程序提交给他。如果此维护者没有回应或者你找不到恰当的
|
||||
维护者,那么请联系 Willy Tarreau <w@1wt.eu>。
|
||||
|
||||
Linux 2.6:
|
||||
除了遵循和 2.4 版内核同样的规则外,你还需要在 linux-kernel 邮件
|
||||
列表上跟踪最新的 API 变化。向 Linux 2.6 内核提交驱动的顶级联系人
|
||||
是 Andrew Morton <akpm@linux-foundation.org>。
|
||||
|
||||
决定设备驱动能否被接受的条件
|
||||
----------------------------
|
||||
|
||||
许可: 代码必须使用 GNU 通用公开许可证 (GPL) 提交给 Linux,但是
|
||||
我们并不要求 GPL 是唯一的许可。你或许会希望同时使用多种
|
||||
许可证发布,如果希望驱动程序可以被其他开源社区(比如BSD)
|
||||
使用。请参考 include/linux/module.h 文件中所列出的可被
|
||||
接受共存的许可。
|
||||
|
||||
版权: 版权所有者必须同意使用 GPL 许可。最好提交者和版权所有者
|
||||
是相同个人或实体。否则,必需列出授权使用 GPL 的版权所有
|
||||
人或实体,以备验证之需。
|
||||
|
||||
接口: 如果你的驱动程序使用现成的接口并且和其他同类的驱动程序行
|
||||
为相似,而不是去发明无谓的新接口,那么它将会更容易被接受。
|
||||
如果你需要一个 Linux 和 NT 的通用驱动接口,那么请在用
|
||||
户空间实现它。
|
||||
|
||||
代码: 请使用 Documentation/CodingStyle 中所描述的 Linux 代码风
|
||||
格。如果你的某些代码段(例如那些与 Windows 驱动程序包共
|
||||
享的代码段)需要使用其他格式,而你却只希望维护一份代码,
|
||||
那么请将它们很好地区分出来,并且注明原因。
|
||||
|
||||
可移植性: 请注意,指针并不永远是 32 位的,不是所有的计算机都使用小
|
||||
尾模式 (little endian) 存储数据,不是所有的人都拥有浮点
|
||||
单元,不要随便在你的驱动程序里嵌入 x86 汇编指令。只能在
|
||||
x86 上运行的驱动程序一般是不受欢迎的。虽然你可能只有 x86
|
||||
硬件,很难测试驱动程序在其他平台上是否可用,但是确保代码
|
||||
可以被轻松地移植却是很简单的。
|
||||
|
||||
清晰度: 做到所有人都能修补这个驱动程序将会很有好处,因为这样你将
|
||||
会直接收到修复的补丁而不是 bug 报告。如果你提交一个试图
|
||||
隐藏硬件工作机理的驱动程序,那么它将会被扔进废纸篓。
|
||||
|
||||
电源管理: 因为 Linux 正在被很多移动设备和桌面系统使用,所以你的驱
|
||||
动程序也很有可能被使用在这些设备上。它应该支持最基本的电
|
||||
源管理,即在需要的情况下实现系统级休眠和唤醒要用到的
|
||||
.suspend 和 .resume 函数。你应该检查你的驱动程序是否能正
|
||||
确地处理休眠与唤醒,如果实在无法确认,请至少把 .suspend
|
||||
函数定义成返回 -ENOSYS(功能未实现)错误。你还应该尝试确
|
||||
保你的驱动在什么都不干的情况下将耗电降到最低。要获得驱动
|
||||
程序测试的指导,请参阅
|
||||
Documentation/power/drivers-testing.txt。有关驱动程序电
|
||||
源管理问题相对全面的概述,请参阅
|
||||
Documentation/power/devices.txt。
|
||||
|
||||
管理: 如果一个驱动程序的作者还在进行有效的维护,那么通常除了那
|
||||
些明显正确且不需要任何检查的补丁以外,其他所有的补丁都会
|
||||
被转发给作者。如果你希望成为驱动程序的联系人和更新者,最
|
||||
好在代码注释中写明并且在 MAINTAINERS 文件中加入这个驱动
|
||||
程序的条目。
|
||||
|
||||
不影响设备驱动能否被接受的条件
|
||||
------------------------------
|
||||
|
||||
供应商: 由硬件供应商来维护驱动程序通常是一件好事。不过,如果源码
|
||||
树里已经有其他人提供了可稳定工作的驱动程序,那么请不要期
|
||||
望“我是供应商”会成为内核改用你的驱动程序的理由。理想的情
|
||||
况是:供应商与现有驱动程序的作者合作,构建一个统一完美的
|
||||
驱动程序。
|
||||
|
||||
作者: 驱动程序是由大的 Linux 公司研发还是由你个人编写,并不影
|
||||
响其是否能被内核接受。没有人对内核源码树享有特权。只要你
|
||||
充分了解内核社区,你就会发现这一点。
|
||||
|
||||
|
||||
资源列表
|
||||
--------
|
||||
|
||||
Linux 内核主源码树:
|
||||
ftp.??.kernel.org:/pub/linux/kernel/...
|
||||
?? == 你的国家代码,例如 "cn"、"us"、"uk"、"fr" 等等
|
||||
|
||||
Linux 内核邮件列表:
|
||||
linux-kernel@vger.kernel.org
|
||||
[可通过向majordomo@vger.kernel.org发邮件来订阅]
|
||||
|
||||
Linux 设备驱动程序,第三版(探讨 2.6.10 版内核):
|
||||
http://lwn.net/Kernel/LDD3/ (免费版)
|
||||
|
||||
LWN.net:
|
||||
每周内核开发活动摘要 - http://lwn.net/
|
||||
2.6 版中 API 的变更:
|
||||
http://lwn.net/Articles/2.6-kernel-api/
|
||||
将旧版内核的驱动程序移植到 2.6 版:
|
||||
http://lwn.net/Articles/driver-porting/
|
||||
|
||||
内核新手(KernelNewbies):
|
||||
为新的内核开发者提供文档和帮助
|
||||
http://kernelnewbies.org/
|
||||
|
||||
Linux USB项目:
|
||||
http://www.linux-usb.org/
|
||||
|
||||
写内核驱动的“不要”(Arjan van de Ven著):
|
||||
http://www.fenrus.org/how-to-not-write-a-device-driver-paper.pdf
|
||||
|
||||
内核清洁工 (Kernel Janitor):
|
||||
http://kernelnewbies.org/KernelJanitors
|
412
Documentation/zh_CN/SubmittingPatches
Normal file
412
Documentation/zh_CN/SubmittingPatches
Normal file
|
@ -0,0 +1,412 @@
|
|||
Chinese translated version of Documentation/SubmittingPatches
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: TripleX Chung <triplex@zh-kernel.org>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/SubmittingPatches 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 钟宇 TripleX Chung <triplex@zh-kernel.org>
|
||||
中文版翻译者: 钟宇 TripleX Chung <triplex@zh-kernel.org>
|
||||
中文版校译者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
王聪 Wang Cong <xiyou.wangcong@gmail.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
如何让你的改动进入内核
|
||||
或者
|
||||
获得亲爱的 Linus Torvalds 的关注和处理
|
||||
----------------------------------
|
||||
|
||||
对于想要将改动提交到 Linux 内核的个人或者公司来说,如果不熟悉“规矩”,
|
||||
提交的流程会让人畏惧。本文档收集了一系列建议,这些建议可以大大的提高你
|
||||
的改动被接受的机会。
|
||||
阅读 Documentation/SubmitChecklist 来获得在提交代码前需要检查的项目的列
|
||||
表。如果你在提交一个驱动程序,那么同时阅读一下
|
||||
Documentation/SubmittingDrivers 。
|
||||
|
||||
|
||||
--------------------------
|
||||
第一节 - 创建并发送你的改动
|
||||
--------------------------
|
||||
|
||||
1) "diff -up"
|
||||
-----------
|
||||
|
||||
使用 "diff -up" 或者 "diff -uprN" 来创建补丁。
|
||||
|
||||
所有内核的改动,都是以补丁的形式呈现的,补丁由 diff(1) 生成。创建补丁的
|
||||
时候,要确认它是以 "unified diff" 格式创建的,这种格式由 diff(1) 的 '-u'
|
||||
参数生成。而且,请使用 '-p' 参数,那样会显示每个改动所在的C函数,使得
|
||||
产生的补丁容易读得多。补丁应该基于内核源代码树的根目录,而不是里边的任
|
||||
何子目录。
|
||||
为一个单独的文件创建补丁,一般来说这样做就够了:
|
||||
|
||||
SRCTREE= linux-2.6
|
||||
MYFILE= drivers/net/mydriver.c
|
||||
|
||||
cd $SRCTREE
|
||||
cp $MYFILE $MYFILE.orig
|
||||
vi $MYFILE # make your change
|
||||
cd ..
|
||||
diff -up $SRCTREE/$MYFILE{.orig,} > /tmp/patch
|
||||
|
||||
为多个文件创建补丁,你可以解开一个没有修改过的内核源代码树,然后和你自
|
||||
己的代码树之间做 diff 。例如:
|
||||
|
||||
MYSRC= /devel/linux-2.6
|
||||
|
||||
tar xvfz linux-2.6.12.tar.gz
|
||||
mv linux-2.6.12 linux-2.6.12-vanilla
|
||||
diff -uprN -X linux-2.6.12-vanilla/Documentation/dontdiff \
|
||||
linux-2.6.12-vanilla $MYSRC > /tmp/patch
|
||||
|
||||
"dontdiff" 是内核在编译的时候产生的文件的列表,列表中的文件在 diff(1)
|
||||
产生的补丁里会被跳过。"dontdiff" 文件被包含在2.6.12和之后版本的内核源代
|
||||
码树中。对于更早的内核版本,你可以从
|
||||
<http://www.xenotime.net/linux/doc/dontdiff> 获取它。
|
||||
确定你的补丁里没有包含任何不属于这次补丁提交的额外文件。记得在用diff(1)
|
||||
生成补丁之后,审阅一次补丁,以确保准确。
|
||||
如果你的改动很散乱,你应该研究一下如何将补丁分割成独立的部分,将改动分
|
||||
割成一系列合乎逻辑的步骤。这样更容易让其他内核开发者审核,如果你想你的
|
||||
补丁被接受,这是很重要的。下面这些脚本能够帮助你做这件事情:
|
||||
Quilt:
|
||||
http://savannah.nongnu.org/projects/quilt
|
||||
|
||||
2)描述你的改动。
|
||||
描述你的改动包含的技术细节。
|
||||
|
||||
要多具体就写多具体。最糟糕的描述可能是像下面这些语句:“更新了某驱动程
|
||||
序”,“修正了某驱动程序的bug”,或者“这个补丁包含了某子系统的修改,请
|
||||
使用。”
|
||||
|
||||
如果你的描述开始变长,这表示你也许需要拆分你的补丁了,请看第3小节,
|
||||
继续。
|
||||
|
||||
3)拆分你的改动
|
||||
|
||||
将改动拆分,逻辑类似的放到同一个补丁文件里。
|
||||
|
||||
例如,如果你的改动里同时有bug修正和性能优化,那么把这些改动拆分到两个或
|
||||
者更多的补丁文件中。如果你的改动包含对API的修改,并且修改了驱动程序来适
|
||||
应这些新的API,那么把这些修改分成两个补丁。
|
||||
|
||||
另一方面,如果你将一个单独的改动做成多个补丁文件,那么将它们合并成一个
|
||||
单独的补丁文件。这样一个逻辑上单独的改动只被包含在一个补丁文件里。
|
||||
|
||||
如果有一个补丁依赖另外一个补丁来完成它的改动,那没问题。简单的在你的补
|
||||
丁描述里指出“这个补丁依赖某补丁”就好了。
|
||||
|
||||
如果你不能将补丁浓缩成更少的文件,那么每次大约发送出15个,然后等待审查
|
||||
和整合。
|
||||
|
||||
4)选择 e-mail 的收件人
|
||||
|
||||
看一遍 MAINTAINERS 文件和源代码,看看你所的改动所在的内核子系统有没有指
|
||||
定的维护者。如果有,给他们发e-mail。
|
||||
|
||||
如果没有找到维护者,或者维护者没有反馈,将你的补丁发送到内核开发者主邮
|
||||
件列表 linux-kernel@vger.kernel.org。大部分的内核开发者都跟踪这个邮件列
|
||||
表,可以评价你的改动。
|
||||
|
||||
每次不要发送超过15个补丁到 vger 邮件列表!!!
|
||||
|
||||
Linus Torvalds 是决定改动能否进入 Linux 内核的最终裁决者。他的 e-mail
|
||||
地址是 <torvalds@linux-foundation.org> 。他收到的 e-mail 很多,所以一般
|
||||
的说,最好别给他发 e-mail。
|
||||
|
||||
那些修正bug,“显而易见”的修改或者是类似的只需要很少讨论的补丁可以直接
|
||||
发送或者CC给Linus。那些需要讨论或者没有很清楚的好处的补丁,一般先发送到
|
||||
linux-kernel邮件列表。只有当补丁被讨论得差不多了,才提交给Linus。
|
||||
|
||||
5)选择CC( e-mail 抄送)列表
|
||||
|
||||
除非你有理由不这样做,否则CC linux-kernel@vger.kernel.org。
|
||||
|
||||
除了 Linus 之外,其他内核开发者也需要注意到你的改动,这样他们才能评论你
|
||||
的改动并提供代码审查和建议。linux-kernel 是 Linux 内核开发者主邮件列表
|
||||
。其它的邮件列表为特定的子系统提供服务,比如 USB,framebuffer 设备,虚
|
||||
拟文件系统,SCSI 子系统,等等。查看 MAINTAINERS 文件来获得和你的改动有
|
||||
关的邮件列表。
|
||||
|
||||
Majordomo lists of VGER.KERNEL.ORG at:
|
||||
<http://vger.kernel.org/vger-lists.html>
|
||||
|
||||
如果改动影响了用户空间和内核之间的接口,请给 MAN-PAGES 的维护者(列在
|
||||
MAINTAINERS 文件里的)发送一个手册页(man-pages)补丁,或者至少通知一下改
|
||||
变,让一些信息有途径进入手册页。
|
||||
|
||||
即使在第四步的时候,维护者没有作出回应,也要确认在修改他们的代码的时候
|
||||
,一直将维护者拷贝到CC列表中。
|
||||
|
||||
对于小的补丁,你也许会CC到 Adrian Bunk 管理的搜集琐碎补丁的邮件列表
|
||||
(Trivial Patch Monkey)trivial@kernel.org,那里专门收集琐碎的补丁。下面这样
|
||||
的补丁会被看作“琐碎的”补丁:
|
||||
文档的拼写修正。
|
||||
修正会影响到 grep(1) 的拼写。
|
||||
警告信息修正(频繁的打印无用的警告是不好的。)
|
||||
编译错误修正(代码逻辑的确是对的,只是编译有问题。)
|
||||
运行时修正(只要真的修正了错误。)
|
||||
移除使用了被废弃的函数/宏的代码(例如 check_region。)
|
||||
联系方式和文档修正。
|
||||
用可移植的代码替换不可移植的代码(即使在体系结构相关的代码中,既然有
|
||||
人拷贝,只要它是琐碎的)
|
||||
任何文件的作者/维护者对该文件的改动(例如 patch monkey 在重传模式下)
|
||||
|
||||
EMAIL: trivial@kernel.org
|
||||
|
||||
(译注,关于“琐碎补丁”的一些说明:因为原文的这一部分写得比较简单,所以不得不
|
||||
违例写一下译注。"trivial"这个英文单词的本意是“琐碎的,不重要的。”但是在这里
|
||||
有稍微有一些变化,例如对一些明显的NULL指针的修正,属于运行时修正,会被归类
|
||||
到琐碎补丁里。虽然NULL指针的修正很重要,但是这样的修正往往很小而且很容易得到
|
||||
检验,所以也被归入琐碎补丁。琐碎补丁更精确的归类应该是
|
||||
“simple, localized & easy to verify”,也就是说简单的,局部的和易于检验的。
|
||||
trivial@kernel.org邮件列表的目的是针对这样的补丁,为提交者提供一个中心,来
|
||||
降低提交的门槛。)
|
||||
|
||||
6)没有 MIME 编码,没有链接,没有压缩,没有附件,只有纯文本。
|
||||
|
||||
Linus 和其他的内核开发者需要阅读和评论你提交的改动。对于内核开发者来说
|
||||
,可以“引用”你的改动很重要,使用一般的 e-mail 工具,他们就可以在你的
|
||||
代码的任何位置添加评论。
|
||||
|
||||
因为这个原因,所有的提交的补丁都是 e-mail 中“内嵌”的。
|
||||
警告:如果你使用剪切-粘贴你的补丁,小心你的编辑器的自动换行功能破坏你的
|
||||
补丁。
|
||||
|
||||
不要将补丁作为 MIME 编码的附件,不管是否压缩。很多流行的 e-mail 软件不
|
||||
是任何时候都将 MIME 编码的附件当作纯文本发送的,这会使得别人无法在你的
|
||||
代码中加评论。另外,MIME 编码的附件会让 Linus 多花一点时间来处理,这就
|
||||
降低了你的改动被接受的可能性。
|
||||
|
||||
警告:一些邮件软件,比如 Mozilla 会将你的信息以如下格式发送:
|
||||
---- 邮件头 ----
|
||||
Content-Type: text/plain; charset=us-ascii; format=flowed
|
||||
---- 邮件头 ----
|
||||
问题在于 “format=flowed” 会让接收端的某些邮件软件将邮件中的制表符替换
|
||||
成空格以及做一些类似的替换。这样,你发送的时候看起来没问题的补丁就被破
|
||||
坏了。
|
||||
|
||||
要修正这个问题,只需要将你的 mozilla 的 defaults/pref/mailnews.js 文件
|
||||
里的
|
||||
pref("mailnews.send_plaintext_flowed", false); // RFC 2646=======
|
||||
修改成
|
||||
pref("mailnews.display.disable_format_flowed_support", true);
|
||||
就可以了。
|
||||
|
||||
7) e-mail 的大小
|
||||
|
||||
给 Linus 发送补丁的时候,永远按照第6小节说的做。
|
||||
|
||||
大的改动对邮件列表不合适,对某些维护者也不合适。如果你的补丁,在不压缩
|
||||
的情况下,超过了40kB,那么你最好将补丁放在一个能通过 internet 访问的服
|
||||
务器上,然后用指向你的补丁的 URL 替代。
|
||||
|
||||
8) 指出你的内核版本
|
||||
|
||||
在标题和在补丁的描述中,指出补丁对应的内核的版本,是很重要的。
|
||||
|
||||
如果补丁不能干净的在最新版本的内核上打上,Linus 是不会接受它的。
|
||||
|
||||
9) 不要气馁,继续提交。
|
||||
|
||||
当你提交了改动以后,耐心地等待。如果 Linus 喜欢你的改动并且同意它,那么
|
||||
它将在下一个内核发布版本中出现。
|
||||
|
||||
然而,如果你的改动没有出现在下一个版本的内核中,可能有若干原因。减少那
|
||||
些原因,修正错误,重新提交更新后的改动,是你自己的工作。
|
||||
|
||||
Linus不给出任何评论就“丢弃”你的补丁是常见的事情。在系统中这样的事情很
|
||||
平常。如果他没有接受你的补丁,也许是由于以下原因:
|
||||
* 你的补丁不能在最新版本的内核上干净的打上。
|
||||
* 你的补丁在 linux-kernel 邮件列表中没有得到充分的讨论。
|
||||
* 风格问题(参照第2小节)
|
||||
* 邮件格式问题(重读本节)
|
||||
* 你的改动有技术问题。
|
||||
* 他收到了成吨的 e-mail,而你的在混乱中丢失了。
|
||||
* 你让人为难。
|
||||
|
||||
有疑问的时候,在 linux-kernel 邮件列表上请求评论。
|
||||
|
||||
10) 在标题上加上 PATCH 的字样
|
||||
|
||||
Linus 和 linux-kernel 邮件列表的 e-mail 流量都很高,一个通常的约定是标
|
||||
题行以 [PATCH] 开头。这样可以让 Linus 和其他内核开发人员可以从 e-mail
|
||||
的讨论中很轻易的将补丁分辨出来。
|
||||
|
||||
11)为你的工作签名
|
||||
|
||||
为了加强对谁做了何事的追踪,尤其是对那些透过好几层的维护者的补丁,我们
|
||||
建议在发送出去的补丁上加一个 “sign-off” 的过程。
|
||||
|
||||
"sign-off" 是在补丁的注释的最后的简单的一行文字,认证你编写了它或者其他
|
||||
人有权力将它作为开放源代码的补丁传递。规则很简单:如果你能认证如下信息
|
||||
:
|
||||
开发者来源证书 1.1
|
||||
对于本项目的贡献,我认证如下信息:
|
||||
(a)这些贡献是完全或者部分的由我创建,我有权利以文件中指出
|
||||
的开放源代码许可证提交它;或者
|
||||
(b)这些贡献基于以前的工作,据我所知,这些以前的工作受恰当的开放
|
||||
源代码许可证保护,而且,根据许可证,我有权提交修改后的贡献,
|
||||
无论是完全还是部分由我创造,这些贡献都使用同一个开放源代码许可证
|
||||
(除非我被允许用其它的许可证),正如文件中指出的;或者
|
||||
(c)这些贡献由认证(a),(b)或者(c)的人直接提供给我,而
|
||||
且我没有修改它。
|
||||
(d)我理解并同意这个项目和贡献是公开的,贡献的记录(包括我
|
||||
一起提交的个人记录,包括 sign-off )被永久维护并且可以和这个项目
|
||||
或者开放源代码的许可证同步地再发行。
|
||||
那么加入这样一行:
|
||||
Signed-off-by: Random J Developer <random@developer.example.org>
|
||||
|
||||
使用你的真名(抱歉,不能使用假名或者匿名。)
|
||||
|
||||
有人在最后加上标签。现在这些东西会被忽略,但是你可以这样做,来标记公司
|
||||
内部的过程,或者只是指出关于 sign-off 的一些特殊细节。
|
||||
|
||||
12)标准补丁格式
|
||||
|
||||
标准的补丁,标题行是:
|
||||
Subject: [PATCH 001/123] 子系统:一句话概述
|
||||
|
||||
标准补丁的信体存在如下部分:
|
||||
|
||||
- 一个 "from" 行指出补丁作者。
|
||||
|
||||
- 一个空行
|
||||
|
||||
- 说明的主体,这些说明文字会被拷贝到描述该补丁的永久改动记录里。
|
||||
|
||||
- 一个由"---"构成的标记行
|
||||
|
||||
- 不合适放到改动记录里的额外的注解。
|
||||
|
||||
- 补丁本身(diff 输出)
|
||||
|
||||
标题行的格式,使得对标题行按字母序排序非常的容易 - 很多 e-mail 客户端都
|
||||
可以支持 - 因为序列号是用零填充的,所以按数字排序和按字母排序是一样的。
|
||||
|
||||
e-mail 标题中的“子系统”标识哪个内核子系统将被打补丁。
|
||||
|
||||
e-mail 标题中的“一句话概述”扼要的描述 e-mail 中的补丁。“一句话概述”
|
||||
不应该是一个文件名。对于一个补丁系列(“补丁系列”指一系列的多个相关补
|
||||
丁),不要对每个补丁都使用同样的“一句话概述”。
|
||||
|
||||
记住 e-mail 的“一句话概述”会成为该补丁的全局唯一标识。它会蔓延到 git
|
||||
的改动记录里。然后“一句话概述”会被用在开发者的讨论里,用来指代这个补
|
||||
丁。用户将希望通过 google 来搜索"一句话概述"来找到那些讨论这个补丁的文
|
||||
章。
|
||||
|
||||
一些标题的例子:
|
||||
|
||||
Subject: [patch 2/5] ext2: improve scalability of bitmap searching
|
||||
Subject: [PATCHv2 001/207] x86: fix eflags tracking
|
||||
|
||||
"from" 行是信体里的最上面一行,具有如下格式:
|
||||
From: Original Author <author@example.com>
|
||||
|
||||
"from" 行指明在永久改动日志里,谁会被确认为作者。如果没有 "from" 行,那
|
||||
么邮件头里的 "From: " 行会被用来决定改动日志中的作者。
|
||||
|
||||
说明的主题将会被提交到永久的源代码改动日志里,因此对那些早已经不记得和
|
||||
这个补丁相关的讨论细节的有能力的读者来说,是有意义的。
|
||||
|
||||
"---" 标记行对于补丁处理工具要找到哪里是改动日志信息的结束,是不可缺少
|
||||
的。
|
||||
|
||||
对于 "---" 标记之后的额外注解,一个好的用途就是用来写 diffstat,用来显
|
||||
示修改了什么文件和每个文件都增加和删除了多少行。diffstat 对于比较大的补
|
||||
丁特别有用。其余那些只是和时刻或者开发者相关的注解,不合适放到永久的改
|
||||
动日志里的,也应该放这里。
|
||||
使用 diffstat的选项 "-p 1 -w 70" 这样文件名就会从内核源代码树的目录开始
|
||||
,不会占用太宽的空间(很容易适合80列的宽度,也许会有一些缩进。)
|
||||
|
||||
在后面的参考资料中能看到适当的补丁格式的更多细节。
|
||||
|
||||
-------------------------------
|
||||
第二节 提示,建议和诀窍
|
||||
-------------------------------
|
||||
|
||||
本节包含很多和提交到内核的代码有关的通常的"规则"。事情永远有例外...但是
|
||||
你必须真的有好的理由这样做。你可以把本节叫做Linus的计算机科学入门课。
|
||||
|
||||
1) 读 Document/CodingStyle
|
||||
|
||||
Nuff 说过,如果你的代码和这个偏离太多,那么它有可能会被拒绝,没有更多的
|
||||
审查,没有更多的评价。
|
||||
|
||||
2) #ifdef 是丑陋的
|
||||
混杂了 ifdef 的代码难以阅读和维护。别这样做。作为替代,将你的 ifdef 放
|
||||
在头文件里,有条件地定义 "static inline" 函数,或者宏,在代码里用这些东
|
||||
西。让编译器把那些"空操作"优化掉。
|
||||
|
||||
一个简单的例子,不好的代码:
|
||||
|
||||
dev = alloc_etherdev (sizeof(struct funky_private));
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
#ifdef CONFIG_NET_FUNKINESS
|
||||
init_funky_net(dev);
|
||||
#endif
|
||||
|
||||
清理后的例子:
|
||||
|
||||
(头文件里)
|
||||
#ifndef CONFIG_NET_FUNKINESS
|
||||
static inline void init_funky_net (struct net_device *d) {}
|
||||
#endif
|
||||
|
||||
(代码文件里)
|
||||
dev = alloc_etherdev (sizeof(struct funky_private));
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
init_funky_net(dev);
|
||||
|
||||
3) 'static inline' 比宏好
|
||||
|
||||
Static inline 函数相比宏来说,是好得多的选择。Static inline 函数提供了
|
||||
类型安全,没有长度限制,没有格式限制,在 gcc 下开销和宏一样小。
|
||||
|
||||
宏只在 static inline 函数不是最优的时候[在 fast paths 里有很少的独立的
|
||||
案例],或者不可能用 static inline 函数的时候[例如字符串分配]。
|
||||
应该用 'static inline' 而不是 'static __inline__', 'extern inline' 和
|
||||
'extern __inline__' 。
|
||||
|
||||
4) 不要过度设计
|
||||
|
||||
不要试图预计模糊的未来事情,这些事情也许有用也许没有用:"让事情尽可能的
|
||||
简单,而不是更简单"。
|
||||
|
||||
----------------
|
||||
第三节 参考文献
|
||||
----------------
|
||||
|
||||
Andrew Morton, "The perfect patch" (tpp).
|
||||
<http://www.ozlabs.org/~akpm/stuff/tpp.txt>
|
||||
|
||||
Jeff Garzik, "Linux kernel patch submission format".
|
||||
<http://linux.yyz.us/patch-format.html>
|
||||
|
||||
Greg Kroah-Hartman, "How to piss off a kernel subsystem maintainer".
|
||||
<http://www.kroah.com/log/2005/03/31/>
|
||||
<http://www.kroah.com/log/2005/07/08/>
|
||||
<http://www.kroah.com/log/2005/10/19/>
|
||||
<http://www.kroah.com/log/2006/01/11/>
|
||||
|
||||
NO!!!! No more huge patch bombs to linux-kernel@vger.kernel.org people!
|
||||
<https://lkml.org/lkml/2005/7/11/336>
|
||||
|
||||
Kernel Documentation/CodingStyle:
|
||||
<http://sosdg.org/~coywolf/lxr/source/Documentation/CodingStyle>
|
||||
|
||||
Linus Torvalds's mail on the canonical patch format:
|
||||
<http://lkml.org/lkml/2005/4/7/183>
|
||||
--
|
175
Documentation/zh_CN/arm/Booting
Normal file
175
Documentation/zh_CN/arm/Booting
Normal file
|
@ -0,0 +1,175 @@
|
|||
Chinese translated version of Documentation/arm/Booting
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Russell King <linux@arm.linux.org.uk>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/arm/Booting 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Russell King <linux@arm.linux.org.uk>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
启动 ARM Linux
|
||||
==============
|
||||
|
||||
作者:Russell King
|
||||
日期:2002年5月18日
|
||||
|
||||
以下文档适用于 2.4.18-rmk6 及以上版本。
|
||||
|
||||
为了启动 ARM Linux,你需要一个引导装载程序(boot loader),
|
||||
它是一个在主内核启动前运行的一个小程序。引导装载程序需要初始化各种
|
||||
设备,并最终调用 Linux 内核,将信息传递给内核。
|
||||
|
||||
从本质上讲,引导装载程序应提供(至少)以下功能:
|
||||
|
||||
1、设置和初始化 RAM。
|
||||
2、初始化一个串口。
|
||||
3、检测机器的类型(machine type)。
|
||||
4、设置内核标签列表(tagged list)。
|
||||
5、调用内核映像。
|
||||
|
||||
|
||||
1、设置和初始化 RAM
|
||||
-------------------
|
||||
|
||||
现有的引导加载程序: 强制
|
||||
新开发的引导加载程序: 强制
|
||||
|
||||
引导装载程序应该找到并初始化系统中所有内核用于保持系统变量数据的 RAM。
|
||||
这个操作的执行是设备依赖的。(它可能使用内部算法来自动定位和计算所有
|
||||
RAM,或可能使用对这个设备已知的 RAM 信息,还可能使用任何引导装载程序
|
||||
设计者想到的匹配方法。)
|
||||
|
||||
|
||||
2、初始化一个串口
|
||||
-----------------------------
|
||||
|
||||
现有的引导加载程序: 可选、建议
|
||||
新开发的引导加载程序: 可选、建议
|
||||
|
||||
引导加载程序应该初始化并使能一个目标板上的串口。这允许内核串口驱动
|
||||
自动检测哪个串口用于内核控制台。(一般用于调试或与目标板通信。)
|
||||
|
||||
作为替代方案,引导加载程序也可以通过标签列表传递相关的'console='
|
||||
选项给内核以指定某个串口,而串口数据格式的选项在以下文档中描述:
|
||||
|
||||
Documentation/kernel-parameters.txt。
|
||||
|
||||
|
||||
3、检测机器类型
|
||||
--------------------------
|
||||
|
||||
现有的引导加载程序: 可选
|
||||
新开发的引导加载程序: 强制
|
||||
|
||||
引导加载程序应该通过某些方式检测自身所处的机器类型。这是一个硬件
|
||||
代码或通过查看所连接的硬件用某些算法得到,这些超出了本文档的范围。
|
||||
引导加载程序最终必须能提供一个 MACH_TYPE_xxx 值给内核。
|
||||
(详见 linux/arch/arm/tools/mach-types )。
|
||||
|
||||
4、设置启动数据
|
||||
------------------
|
||||
|
||||
现有的引导加载程序: 可选、强烈建议
|
||||
新开发的引导加载程序: 强制
|
||||
|
||||
引导加载程序必须提供标签列表或者 dtb 映像以传递配置数据给内核。启动
|
||||
数据的物理地址通过寄存器 r2 传递给内核。
|
||||
|
||||
4a、设置内核标签列表
|
||||
--------------------------------
|
||||
|
||||
bootloader 必须创建和初始化内核标签列表。一个有效的标签列表以
|
||||
ATAG_CORE 标签开始,并以 ATAG_NONE 标签结束。ATAG_CORE 标签可以是
|
||||
空的,也可以是非空。一个空 ATAG_CORE 标签其 size 域设置为
|
||||
‘2’(0x00000002)。ATAG_NONE 标签的 size 域必须设置为零。
|
||||
|
||||
在列表中可以保存任意数量的标签。对于一个重复的标签是追加到之前标签
|
||||
所携带的信息之后,还是会覆盖原来的信息,是未定义的。某些标签的行为
|
||||
是前者,其他是后者。
|
||||
|
||||
bootloader 必须传递一个系统内存的位置和最小值,以及根文件系统位置。
|
||||
因此,最小的标签列表如下所示:
|
||||
|
||||
+-----------+
|
||||
基地址 -> | ATAG_CORE | |
|
||||
+-----------+ |
|
||||
| ATAG_MEM | | 地址增长方向
|
||||
+-----------+ |
|
||||
| ATAG_NONE | |
|
||||
+-----------+ v
|
||||
|
||||
标签列表应该保存在系统的 RAM 中。
|
||||
|
||||
标签列表必须置于内核自解压和 initrd'bootp' 程序都不会覆盖的内存区。
|
||||
建议放在 RAM 的头 16KiB 中。
|
||||
|
||||
4b、设置设备树
|
||||
-------------------------
|
||||
|
||||
bootloader 必须以 64bit 地址对齐的形式加载一个设备树映像(dtb)到系统
|
||||
RAM 中,并用启动数据初始化它。dtb 格式在文档
|
||||
Documentation/devicetree/booting-without-of.txt 中。内核将会在
|
||||
dtb 物理地址处查找 dtb 魔数值(0xd00dfeed),以确定 dtb 是否已经代替
|
||||
标签列表被传递进来。
|
||||
|
||||
bootloader 必须传递一个系统内存的位置和最小值,以及根文件系统位置。
|
||||
dtb 必须置于内核自解压不会覆盖的内存区。建议将其放置于 RAM 的头 16KiB
|
||||
中。但是不可将其放置于“0”物理地址处,因为内核认为:r2 中为 0,意味着
|
||||
没有标签列表和 dtb 传递过来。
|
||||
|
||||
5、调用内核映像
|
||||
---------------------------
|
||||
|
||||
现有的引导加载程序: 强制
|
||||
新开发的引导加载程序: 强制
|
||||
|
||||
调用内核映像 zImage 有两个选择。如果 zImge 保存在 flash 中,且是为了
|
||||
在 flash 中直接运行而被正确链接的。这样引导加载程序就可以在 flash 中
|
||||
直接调用 zImage。
|
||||
|
||||
zImage 也可以被放在系统 RAM(任意位置)中被调用。注意:内核使用映像
|
||||
基地址的前 16KB RAM 空间来保存页表。建议将映像置于 RAM 的 32KB 处。
|
||||
|
||||
对于以上任意一种情况,都必须符合以下启动状态:
|
||||
|
||||
- 停止所有 DMA 设备,这样内存数据就不会因为虚假网络包或磁盘数据而被破坏。
|
||||
这可能可以节省你许多的调试时间。
|
||||
|
||||
- CPU 寄存器配置
|
||||
r0 = 0,
|
||||
r1 = (在上面 3 中获取的)机器类型码。
|
||||
r2 = 标签列表在系统 RAM 中的物理地址,或
|
||||
设备树块(dtb)在系统 RAM 中的物理地址
|
||||
|
||||
- CPU 模式
|
||||
所有形式的中断必须被禁止 (IRQs 和 FIQs)
|
||||
CPU 必须处于 SVC 模式。(对于 Angel 调试有特例存在)
|
||||
|
||||
- 缓存,MMUs
|
||||
MMU 必须关闭。
|
||||
指令缓存开启或关闭都可以。
|
||||
数据缓存必须关闭。
|
||||
|
||||
- 引导加载程序应该通过直接跳转到内核映像的第一条指令来调用内核映像。
|
||||
|
||||
对于支持 ARM 指令集的 CPU,跳入内核入口时必须处在 ARM 状态,即使
|
||||
对于 Thumb-2 内核也是如此。
|
||||
|
||||
对于仅支持 Thumb 指令集的 CPU,比如 Cortex-M 系列的 CPU,跳入
|
||||
内核入口时必须处于 Thumb 状态。
|
284
Documentation/zh_CN/arm/kernel_user_helpers.txt
Normal file
284
Documentation/zh_CN/arm/kernel_user_helpers.txt
Normal file
|
@ -0,0 +1,284 @@
|
|||
Chinese translated version of Documentation/arm/kernel_user_helpers.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Nicolas Pitre <nicolas.pitre@linaro.org>
|
||||
Dave Martin <dave.martin@linaro.org>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/arm/kernel_user_helpers.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
英文版维护者: Nicolas Pitre <nicolas.pitre@linaro.org>
|
||||
Dave Martin <dave.martin@linaro.org>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 宋冬生 Dongsheng Song <dongshneg.song@gmail.com>
|
||||
傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
内核提供的用户空间辅助代码
|
||||
=========================
|
||||
|
||||
在内核内存空间的固定地址处,有一个由内核提供并可从用户空间访问的代码
|
||||
段。它用于向用户空间提供因在许多 ARM CPU 中未实现的特性和/或指令而需
|
||||
内核提供帮助的某些操作。这些代码直接在用户模式下执行的想法是为了获得
|
||||
最佳效率,但那些与内核计数器联系过于紧密的部分,则被留给了用户库实现。
|
||||
事实上,此代码甚至可能因不同的 CPU 而异,这取决于其可用的指令集或它
|
||||
是否为 SMP 系统。换句话说,内核保留在不作出警告的情况下根据需要更改
|
||||
这些代码的权利。只有本文档描述的入口及其结果是保证稳定的。
|
||||
|
||||
这与完全成熟的 VDSO 实现不同(但两者并不冲突),尽管如此,VDSO 可阻止
|
||||
某些通过常量高效跳转到那些代码段的汇编技巧。且由于那些代码段在返回用户
|
||||
代码前仅使用少量的代码周期,则一个 VDSO 间接远程调用将会在这些简单的
|
||||
操作上增加一个可测量的开销。
|
||||
|
||||
在对那些拥有原生支持的新型处理器进行代码优化时,仅在已为其他操作使用
|
||||
了类似的新增指令,而导致二进制结果已与早期 ARM 处理器不兼容的情况下,
|
||||
用户空间才应绕过这些辅助代码,并在内联函数中实现这些操作(无论是通过
|
||||
编译器在代码中直接放置,还是作为库函数调用实现的一部分)。也就是说,
|
||||
如果你编译的代码不会为了其他目的使用新指令,则不要仅为了避免使用这些
|
||||
内核辅助代码,导致二进制程序无法在早期处理器上运行。
|
||||
|
||||
新的辅助代码可能随着时间的推移而增加,所以新内核中的某些辅助代码在旧
|
||||
内核中可能不存在。因此,程序必须在对任何辅助代码调用假设是安全之前,
|
||||
检测 __kuser_helper_version 的值(见下文)。理想情况下,这种检测应该
|
||||
只在进程启动时执行一次;如果内核版本不支持所需辅助代码,则该进程可尽早
|
||||
中止执行。
|
||||
|
||||
kuser_helper_version
|
||||
--------------------
|
||||
|
||||
位置: 0xffff0ffc
|
||||
|
||||
参考声明:
|
||||
|
||||
extern int32_t __kuser_helper_version;
|
||||
|
||||
定义:
|
||||
|
||||
这个区域包含了当前运行内核实现的辅助代码版本号。用户空间可以通过读
|
||||
取此版本号以确定特定的辅助代码是否存在。
|
||||
|
||||
使用范例:
|
||||
|
||||
#define __kuser_helper_version (*(int32_t *)0xffff0ffc)
|
||||
|
||||
void check_kuser_version(void)
|
||||
{
|
||||
if (__kuser_helper_version < 2) {
|
||||
fprintf(stderr, "can't do atomic operations, kernel too old\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
注意:
|
||||
|
||||
用户空间可以假设这个域的值不会在任何单个进程的生存期内改变。也就
|
||||
是说,这个域可以仅在库的初始化阶段或进程启动阶段读取一次。
|
||||
|
||||
kuser_get_tls
|
||||
-------------
|
||||
|
||||
位置: 0xffff0fe0
|
||||
|
||||
参考原型:
|
||||
|
||||
void * __kuser_get_tls(void);
|
||||
|
||||
输入:
|
||||
|
||||
lr = 返回地址
|
||||
|
||||
输出:
|
||||
|
||||
r0 = TLS 值
|
||||
|
||||
被篡改的寄存器:
|
||||
|
||||
无
|
||||
|
||||
定义:
|
||||
|
||||
获取之前通过 __ARM_NR_set_tls 系统调用设置的 TLS 值。
|
||||
|
||||
使用范例:
|
||||
|
||||
typedef void * (__kuser_get_tls_t)(void);
|
||||
#define __kuser_get_tls (*(__kuser_get_tls_t *)0xffff0fe0)
|
||||
|
||||
void foo()
|
||||
{
|
||||
void *tls = __kuser_get_tls();
|
||||
printf("TLS = %p\n", tls);
|
||||
}
|
||||
|
||||
注意:
|
||||
|
||||
- 仅在 __kuser_helper_version >= 1 时,此辅助代码存在
|
||||
(从内核版本 2.6.12 开始)。
|
||||
|
||||
kuser_cmpxchg
|
||||
-------------
|
||||
|
||||
位置: 0xffff0fc0
|
||||
|
||||
参考原型:
|
||||
|
||||
int __kuser_cmpxchg(int32_t oldval, int32_t newval, volatile int32_t *ptr);
|
||||
|
||||
输入:
|
||||
|
||||
r0 = oldval
|
||||
r1 = newval
|
||||
r2 = ptr
|
||||
lr = 返回地址
|
||||
|
||||
输出:
|
||||
|
||||
r0 = 成功代码 (零或非零)
|
||||
C flag = 如果 r0 == 0 则置 1,如果 r0 != 0 则清零。
|
||||
|
||||
被篡改的寄存器:
|
||||
|
||||
r3, ip, flags
|
||||
|
||||
定义:
|
||||
|
||||
仅在 *ptr 为 oldval 时原子保存 newval 于 *ptr 中。
|
||||
如果 *ptr 被改变,则返回值为零,否则为非零值。
|
||||
如果 *ptr 被改变,则 C flag 也会被置 1,以实现调用代码中的汇编
|
||||
优化。
|
||||
|
||||
使用范例:
|
||||
|
||||
typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
|
||||
#define __kuser_cmpxchg (*(__kuser_cmpxchg_t *)0xffff0fc0)
|
||||
|
||||
int atomic_add(volatile int *ptr, int val)
|
||||
{
|
||||
int old, new;
|
||||
|
||||
do {
|
||||
old = *ptr;
|
||||
new = old + val;
|
||||
} while(__kuser_cmpxchg(old, new, ptr));
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
注意:
|
||||
|
||||
- 这个例程已根据需要包含了内存屏障。
|
||||
|
||||
- 仅在 __kuser_helper_version >= 2 时,此辅助代码存在
|
||||
(从内核版本 2.6.12 开始)。
|
||||
|
||||
kuser_memory_barrier
|
||||
--------------------
|
||||
|
||||
位置: 0xffff0fa0
|
||||
|
||||
参考原型:
|
||||
|
||||
void __kuser_memory_barrier(void);
|
||||
|
||||
输入:
|
||||
|
||||
lr = 返回地址
|
||||
|
||||
输出:
|
||||
|
||||
无
|
||||
|
||||
被篡改的寄存器:
|
||||
|
||||
无
|
||||
|
||||
定义:
|
||||
|
||||
应用于任何需要内存屏障以防止手动数据修改带来的一致性问题,以及
|
||||
__kuser_cmpxchg 中。
|
||||
|
||||
使用范例:
|
||||
|
||||
typedef void (__kuser_dmb_t)(void);
|
||||
#define __kuser_dmb (*(__kuser_dmb_t *)0xffff0fa0)
|
||||
|
||||
注意:
|
||||
|
||||
- 仅在 __kuser_helper_version >= 3 时,此辅助代码存在
|
||||
(从内核版本 2.6.15 开始)。
|
||||
|
||||
kuser_cmpxchg64
|
||||
---------------
|
||||
|
||||
位置: 0xffff0f60
|
||||
|
||||
参考原型:
|
||||
|
||||
int __kuser_cmpxchg64(const int64_t *oldval,
|
||||
const int64_t *newval,
|
||||
volatile int64_t *ptr);
|
||||
|
||||
输入:
|
||||
|
||||
r0 = 指向 oldval
|
||||
r1 = 指向 newval
|
||||
r2 = 指向目标值
|
||||
lr = 返回地址
|
||||
|
||||
输出:
|
||||
|
||||
r0 = 成功代码 (零或非零)
|
||||
C flag = 如果 r0 == 0 则置 1,如果 r0 != 0 则清零。
|
||||
|
||||
被篡改的寄存器:
|
||||
|
||||
r3, lr, flags
|
||||
|
||||
定义:
|
||||
|
||||
仅在 *ptr 等于 *oldval 指向的 64 位值时,原子保存 *newval
|
||||
指向的 64 位值于 *ptr 中。如果 *ptr 被改变,则返回值为零,
|
||||
否则为非零值。
|
||||
|
||||
如果 *ptr 被改变,则 C flag 也会被置 1,以实现调用代码中的汇编
|
||||
优化。
|
||||
|
||||
使用范例:
|
||||
|
||||
typedef int (__kuser_cmpxchg64_t)(const int64_t *oldval,
|
||||
const int64_t *newval,
|
||||
volatile int64_t *ptr);
|
||||
#define __kuser_cmpxchg64 (*(__kuser_cmpxchg64_t *)0xffff0f60)
|
||||
|
||||
int64_t atomic_add64(volatile int64_t *ptr, int64_t val)
|
||||
{
|
||||
int64_t old, new;
|
||||
|
||||
do {
|
||||
old = *ptr;
|
||||
new = old + val;
|
||||
} while(__kuser_cmpxchg64(&old, &new, ptr));
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
注意:
|
||||
|
||||
- 这个例程已根据需要包含了内存屏障。
|
||||
|
||||
- 由于这个过程的代码长度(此辅助代码跨越 2 个常规的 kuser “槽”),
|
||||
因此 0xffff0f80 不被作为有效的入口点。
|
||||
|
||||
- 仅在 __kuser_helper_version >= 5 时,此辅助代码存在
|
||||
(从内核版本 3.1 开始)。
|
183
Documentation/zh_CN/arm64/booting.txt
Normal file
183
Documentation/zh_CN/arm64/booting.txt
Normal file
|
@ -0,0 +1,183 @@
|
|||
Chinese translated version of Documentation/arm64/booting.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Will Deacon <will.deacon@arm.com>
|
||||
Chinese maintainer: Fu Wei <wefu@redhat.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/arm64/booting.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Will Deacon <will.deacon@arm.com>
|
||||
中文版维护者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
中文版校译者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
启动 AArch64 Linux
|
||||
==================
|
||||
|
||||
作者: Will Deacon <will.deacon@arm.com>
|
||||
日期: 2012 年 09 月 07 日
|
||||
|
||||
本文档基于 Russell King 的 ARM 启动文档,且适用于所有公开发布的
|
||||
AArch64 Linux 内核代码。
|
||||
|
||||
AArch64 异常模型由多个异常级别(EL0 - EL3)组成,对于 EL0 和 EL1
|
||||
异常级有对应的安全和非安全模式。EL2 是系统管理级,且仅存在于
|
||||
非安全模式下。EL3 是最高特权级,且仅存在于安全模式下。
|
||||
|
||||
基于本文档的目的,我们将简单地使用‘引导装载程序’(‘boot loader’)
|
||||
这个术语来定义在将控制权交给 Linux 内核前 CPU 上执行的所有软件。
|
||||
这可能包含安全监控和系统管理代码,或者它可能只是一些用于准备最小启动
|
||||
环境的指令。
|
||||
|
||||
基本上,引导装载程序(至少)应实现以下操作:
|
||||
|
||||
1、设置和初始化 RAM
|
||||
2、设置设备树数据
|
||||
3、解压内核映像
|
||||
4、调用内核映像
|
||||
|
||||
|
||||
1、设置和初始化 RAM
|
||||
-----------------
|
||||
|
||||
必要性: 强制
|
||||
|
||||
引导装载程序应该找到并初始化系统中所有内核用于保持系统变量数据的 RAM。
|
||||
这个操作的执行是设备依赖的。(它可能使用内部算法来自动定位和计算所有
|
||||
RAM,或可能使用对这个设备已知的 RAM 信息,还可能使用任何引导装载程序
|
||||
设计者想到的匹配方法。)
|
||||
|
||||
|
||||
2、设置设备树数据
|
||||
---------------
|
||||
|
||||
必要性: 强制
|
||||
|
||||
设备树数据块(dtb)必须 8 字节对齐,并位于从内核映像起始算起第一个 512MB
|
||||
内,且不得跨越 2MB 对齐边界。这使得内核可以通过初始页表中的单个节描述符来
|
||||
映射此数据块。
|
||||
|
||||
|
||||
3、解压内核映像
|
||||
-------------
|
||||
|
||||
必要性: 可选
|
||||
|
||||
AArch64 内核当前没有提供自解压代码,因此如果使用了压缩内核映像文件
|
||||
(比如 Image.gz),则需要通过引导装载程序(使用 gzip 等)来进行解压。
|
||||
若引导装载程序没有实现这个需求,就要使用非压缩内核映像文件。
|
||||
|
||||
|
||||
4、调用内核映像
|
||||
-------------
|
||||
|
||||
必要性: 强制
|
||||
|
||||
已解压的内核映像包含一个 64 字节的头,内容如下:
|
||||
|
||||
u32 code0; /* 可执行代码 */
|
||||
u32 code1; /* 可执行代码 */
|
||||
u64 text_offset; /* 映像装载偏移 */
|
||||
u64 res0 = 0; /* 保留 */
|
||||
u64 res1 = 0; /* 保留 */
|
||||
u64 res2 = 0; /* 保留 */
|
||||
u64 res3 = 0; /* 保留 */
|
||||
u64 res4 = 0; /* 保留 */
|
||||
u32 magic = 0x644d5241; /* 魔数, 小端, "ARM\x64" */
|
||||
u32 res5 = 0; /* 保留 */
|
||||
|
||||
|
||||
映像头注释:
|
||||
|
||||
- code0/code1 负责跳转到 stext.
|
||||
|
||||
映像必须位于系统 RAM 起始处的特定偏移(当前是 0x80000)。系统 RAM
|
||||
的起始地址必须是以 2MB 对齐的。
|
||||
|
||||
在跳转入内核前,必须符合以下状态:
|
||||
|
||||
- 停止所有 DMA 设备,这样内存数据就不会因为虚假网络包或磁盘数据而
|
||||
被破坏。这可能可以节省你许多的调试时间。
|
||||
|
||||
- 主 CPU 通用寄存器设置
|
||||
x0 = 系统 RAM 中设备树数据块(dtb)的物理地址。
|
||||
x1 = 0 (保留,将来可能使用)
|
||||
x2 = 0 (保留,将来可能使用)
|
||||
x3 = 0 (保留,将来可能使用)
|
||||
|
||||
- CPU 模式
|
||||
所有形式的中断必须在 PSTATE.DAIF 中被屏蔽(Debug、SError、IRQ
|
||||
和 FIQ)。
|
||||
CPU 必须处于 EL2(推荐,可访问虚拟化扩展)或非安全 EL1 模式下。
|
||||
|
||||
- 高速缓存、MMU
|
||||
MMU 必须关闭。
|
||||
指令缓存开启或关闭都可以。
|
||||
数据缓存必须关闭且无效。
|
||||
外部高速缓存(如果存在)必须配置并禁用。
|
||||
|
||||
- 架构计时器
|
||||
CNTFRQ 必须设定为计时器的频率,且 CNTVOFF 必须设定为对所有 CPU
|
||||
都一致的值。如果在 EL1 模式下进入内核,则 CNTHCTL_EL2 中的
|
||||
EL1PCTEN (bit 0) 必须置位。
|
||||
|
||||
- 一致性
|
||||
通过内核启动的所有 CPU 在内核入口地址上必须处于相同的一致性域中。
|
||||
这可能要根据具体实现来定义初始化过程,以使能每个CPU上对维护操作的
|
||||
接收。
|
||||
|
||||
- 系统寄存器
|
||||
在进入内核映像的异常级中,所有构架中可写的系统寄存器必须通过软件
|
||||
在一个更高的异常级别下初始化,以防止在 未知 状态下运行。
|
||||
|
||||
以上对于 CPU 模式、高速缓存、MMU、架构计时器、一致性、系统寄存器的
|
||||
必要条件描述适用于所有 CPU。所有 CPU 必须在同一异常级别跳入内核。
|
||||
|
||||
引导装载程序必须在每个 CPU 处于以下状态时跳入内核入口:
|
||||
|
||||
- 主 CPU 必须直接跳入内核映像的第一条指令。通过此 CPU 传递的设备树
|
||||
数据块必须在每个 CPU 节点中包含一个 ‘enable-method’ 属性,所
|
||||
支持的 enable-method 请见下文。
|
||||
|
||||
引导装载程序必须生成这些设备树属性,并在跳入内核入口之前将其插入
|
||||
数据块。
|
||||
|
||||
- enable-method 为 “spin-table” 的 CPU 必须在它们的 CPU
|
||||
节点中包含一个 ‘cpu-release-addr’ 属性。这个属性标识了一个
|
||||
64 位自然对齐且初始化为零的内存位置。
|
||||
|
||||
这些 CPU 必须在内存保留区(通过设备树中的 /memreserve/ 域传递
|
||||
给内核)中自旋于内核之外,轮询它们的 cpu-release-addr 位置(必须
|
||||
包含在保留区中)。可通过插入 wfe 指令来降低忙循环开销,而主 CPU 将
|
||||
发出 sev 指令。当对 cpu-release-addr 所指位置的读取操作返回非零值
|
||||
时,CPU 必须跳入此值所指向的地址。此值为一个单独的 64 位小端值,
|
||||
因此 CPU 须在跳转前将所读取的值转换为其本身的端模式。
|
||||
|
||||
- enable-method 为 “psci” 的 CPU 保持在内核外(比如,在
|
||||
memory 节点中描述为内核空间的内存区外,或在通过设备树 /memreserve/
|
||||
域中描述为内核保留区的空间中)。内核将会发起在 ARM 文档(编号
|
||||
ARM DEN 0022A:用于 ARM 上的电源状态协调接口系统软件)中描述的
|
||||
CPU_ON 调用来将 CPU 带入内核。
|
||||
|
||||
*译者注:到文档翻译时,此文档已更新为 ARM DEN 0022B。
|
||||
|
||||
设备树必须包含一个 ‘psci’ 节点,请参考以下文档:
|
||||
Documentation/devicetree/bindings/arm/psci.txt
|
||||
|
||||
|
||||
- 辅助 CPU 通用寄存器设置
|
||||
x0 = 0 (保留,将来可能使用)
|
||||
x1 = 0 (保留,将来可能使用)
|
||||
x2 = 0 (保留,将来可能使用)
|
||||
x3 = 0 (保留,将来可能使用)
|
127
Documentation/zh_CN/arm64/memory.txt
Normal file
127
Documentation/zh_CN/arm64/memory.txt
Normal file
|
@ -0,0 +1,127 @@
|
|||
Chinese translated version of Documentation/arm64/memory.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Catalin Marinas <catalin.marinas@arm.com>
|
||||
Chinese maintainer: Fu Wei <wefu@redhat.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/arm64/memory.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Catalin Marinas <catalin.marinas@arm.com>
|
||||
中文版维护者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
中文版校译者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
Linux 在 AArch64 中的内存布局
|
||||
===========================
|
||||
|
||||
作者: Catalin Marinas <catalin.marinas@arm.com>
|
||||
日期: 2012 年 02 月 20 日
|
||||
|
||||
本文档描述 AArch64 Linux 内核所使用的虚拟内存布局。此构架可以实现
|
||||
页大小为 4KB 的 4 级转换表和页大小为 64KB 的 3 级转换表。
|
||||
|
||||
AArch64 Linux 使用页大小为 4KB 的 3 级转换表配置,对于用户和内核
|
||||
都有 39-bit (512GB) 的虚拟地址空间。对于页大小为 64KB的配置,仅
|
||||
使用 2 级转换表,但内存布局相同。
|
||||
|
||||
用户地址空间的 63:39 位为 0,而内核地址空间的相应位为 1。TTBRx 的
|
||||
选择由虚拟地址的 63 位给出。swapper_pg_dir 仅包含内核(全局)映射,
|
||||
而用户 pgd 仅包含用户(非全局)映射。swapper_pgd_dir 地址被写入
|
||||
TTBR1 中,且从不写入 TTBR0。
|
||||
|
||||
|
||||
AArch64 Linux 在页大小为 4KB 时的内存布局:
|
||||
|
||||
起始地址 结束地址 大小 用途
|
||||
-----------------------------------------------------------------------
|
||||
0000000000000000 0000007fffffffff 512GB 用户空间
|
||||
|
||||
ffffff8000000000 ffffffbbfffeffff ~240GB vmalloc
|
||||
|
||||
ffffffbbffff0000 ffffffbbffffffff 64KB [防护页]
|
||||
|
||||
ffffffbc00000000 ffffffbdffffffff 8GB vmemmap
|
||||
|
||||
ffffffbe00000000 ffffffbffbbfffff ~8GB [防护页,未来用于 vmmemap]
|
||||
|
||||
ffffffbffbc00000 ffffffbffbdfffff 2MB earlyprintk 设备
|
||||
|
||||
ffffffbffbe00000 ffffffbffbe0ffff 64KB PCI I/O 空间
|
||||
|
||||
ffffffbffbe10000 ffffffbcffffffff ~2MB [防护页]
|
||||
|
||||
ffffffbffc000000 ffffffbfffffffff 64MB 模块
|
||||
|
||||
ffffffc000000000 ffffffffffffffff 256GB 内核逻辑内存映射
|
||||
|
||||
|
||||
AArch64 Linux 在页大小为 64KB 时的内存布局:
|
||||
|
||||
起始地址 结束地址 大小 用途
|
||||
-----------------------------------------------------------------------
|
||||
0000000000000000 000003ffffffffff 4TB 用户空间
|
||||
|
||||
fffffc0000000000 fffffdfbfffeffff ~2TB vmalloc
|
||||
|
||||
fffffdfbffff0000 fffffdfbffffffff 64KB [防护页]
|
||||
|
||||
fffffdfc00000000 fffffdfdffffffff 8GB vmemmap
|
||||
|
||||
fffffdfe00000000 fffffdfffbbfffff ~8GB [防护页,未来用于 vmmemap]
|
||||
|
||||
fffffdfffbc00000 fffffdfffbdfffff 2MB earlyprintk 设备
|
||||
|
||||
fffffdfffbe00000 fffffdfffbe0ffff 64KB PCI I/O 空间
|
||||
|
||||
fffffdfffbe10000 fffffdfffbffffff ~2MB [防护页]
|
||||
|
||||
fffffdfffc000000 fffffdffffffffff 64MB 模块
|
||||
|
||||
fffffe0000000000 ffffffffffffffff 2TB 内核逻辑内存映射
|
||||
|
||||
|
||||
4KB 页大小的转换表查找:
|
||||
|
||||
+--------+--------+--------+--------+--------+--------+--------+--------+
|
||||
|63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0|
|
||||
+--------+--------+--------+--------+--------+--------+--------+--------+
|
||||
| | | | | |
|
||||
| | | | | v
|
||||
| | | | | [11:0] 页内偏移
|
||||
| | | | +-> [20:12] L3 索引
|
||||
| | | +-----------> [29:21] L2 索引
|
||||
| | +---------------------> [38:30] L1 索引
|
||||
| +-------------------------------> [47:39] L0 索引 (未使用)
|
||||
+-------------------------------------------------> [63] TTBR0/1
|
||||
|
||||
|
||||
64KB 页大小的转换表查找:
|
||||
|
||||
+--------+--------+--------+--------+--------+--------+--------+--------+
|
||||
|63 56|55 48|47 40|39 32|31 24|23 16|15 8|7 0|
|
||||
+--------+--------+--------+--------+--------+--------+--------+--------+
|
||||
| | | | |
|
||||
| | | | v
|
||||
| | | | [15:0] 页内偏移
|
||||
| | | +----------> [28:16] L3 索引
|
||||
| | +--------------------------> [41:29] L2 索引 (仅使用 38:29 )
|
||||
| +-------------------------------> [47:42] L1 索引 (未使用)
|
||||
+-------------------------------------------------> [63] TTBR0/1
|
||||
|
||||
当使用 KVM 时, 管理程序(hypervisor)在 EL2 中通过相对内核虚拟地址的
|
||||
一个固定偏移来映射内核页(内核虚拟地址的高 24 位设为零):
|
||||
|
||||
起始地址 结束地址 大小 用途
|
||||
-----------------------------------------------------------------------
|
||||
0000004000000000 0000007fffffffff 256GB 在 HYP 中映射的内核对象
|
52
Documentation/zh_CN/arm64/tagged-pointers.txt
Normal file
52
Documentation/zh_CN/arm64/tagged-pointers.txt
Normal file
|
@ -0,0 +1,52 @@
|
|||
Chinese translated version of Documentation/arm64/tagged-pointers.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Will Deacon <will.deacon@arm.com>
|
||||
Chinese maintainer: Fu Wei <wefu@redhat.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/arm64/tagged-pointers.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Will Deacon <will.deacon@arm.com>
|
||||
中文版维护者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
中文版校译者: 傅炜 Fu Wei <wefu@redhat.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
Linux 在 AArch64 中带标记的虚拟地址
|
||||
=================================
|
||||
|
||||
作者: Will Deacon <will.deacon@arm.com>
|
||||
日期: 2013 年 06 月 12 日
|
||||
|
||||
本文档简述了在 AArch64 地址转换系统中提供的带标记的虚拟地址及其在
|
||||
AArch64 Linux 中的潜在用途。
|
||||
|
||||
内核提供的地址转换表配置使通过 TTBR0 完成的虚拟地址转换(即用户空间
|
||||
映射),其虚拟地址的最高 8 位(63:56)会被转换硬件所忽略。这种机制
|
||||
让这些位可供应用程序自由使用,其注意事项如下:
|
||||
|
||||
(1) 内核要求所有传递到 EL1 的用户空间地址带有 0x00 标记。
|
||||
这意味着任何携带用户空间虚拟地址的系统调用(syscall)
|
||||
参数 *必须* 在陷入内核前使它们的最高字节被清零。
|
||||
|
||||
(2) 非零标记在传递信号时不被保存。这意味着在应用程序中利用了
|
||||
标记的信号处理函数无法依赖 siginfo_t 的用户空间虚拟
|
||||
地址所携带的包含其内部域信息的标记。此规则的一个例外是
|
||||
当信号是在调试观察点的异常处理程序中产生的,此时标记的
|
||||
信息将被保存。
|
||||
|
||||
(3) 当使用带标记的指针时需特别留心,因为仅对两个虚拟地址
|
||||
的高字节,C 编译器很可能无法判断它们是不同的。
|
||||
|
||||
此构架会阻止对带标记的 PC 指针的利用,因此在异常返回时,其高字节
|
||||
将被设置成一个为 “55” 的扩展符。
|
71
Documentation/zh_CN/basic_profiling.txt
Normal file
71
Documentation/zh_CN/basic_profiling.txt
Normal file
|
@ -0,0 +1,71 @@
|
|||
Chinese translated version of Documentation/basic_profiling
|
||||
|
||||
If you have any comment or update to the content, please post to LKML directly.
|
||||
However, if you have problem communicating in English you can also ask the
|
||||
Chinese maintainer for help. Contact the Chinese maintainer, if this
|
||||
translation is outdated or there is problem with translation.
|
||||
|
||||
Chinese maintainer: Liang Xie <xieliang@xiaomi.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/basic_profiling的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接发信到LKML。如果你使用英文交流有困难的话,也可
|
||||
以向中文版维护者求助。如果本翻译更新不及时或者翻译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 谢良 Liang Xie <xieliang007@gmail.com>
|
||||
中文版翻译者: 谢良 Liang Xie <xieliang007@gmail.com>
|
||||
中文版校译者:
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
下面这些说明指令都是非常基础的,如果你想进一步了解请阅读相关专业文档:)
|
||||
请不要再在本文档增加新的内容,但可以修复文档中的错误:)(mbligh@aracnet.com)
|
||||
感谢John Levon,Dave Hansen等在撰写时的帮助
|
||||
|
||||
<test> 用于表示要测量的目标
|
||||
请先确保您已经有正确的System.map / vmlinux配置!
|
||||
|
||||
对于linux系统来说,配置vmlinuz最容易的方法可能就是使用“make install”,然后修改
|
||||
/sbin/installkernel将vmlinux拷贝到/boot目录,而System.map通常是默认安装好的
|
||||
|
||||
Readprofile
|
||||
-----------
|
||||
2.6系列内核需要版本相对较新的readprofile,比如util-linux 2.12a中包含的,可以从:
|
||||
|
||||
http://www.kernel.org/pub/linux/utils/util-linux/ 下载
|
||||
|
||||
大部分linux发行版已经包含了.
|
||||
|
||||
启用readprofile需要在kernel启动命令行增加”profile=2“
|
||||
|
||||
clear readprofile -r
|
||||
<test>
|
||||
dump output readprofile -m /boot/System.map > captured_profile
|
||||
|
||||
Oprofile
|
||||
--------
|
||||
|
||||
从http://oprofile.sourceforge.net/获取源代码(请参考Changes以获取匹配的版本)
|
||||
在kernel启动命令行增加“idle=poll”
|
||||
|
||||
配置CONFIG_PROFILING=y和CONFIG_OPROFILE=y然后重启进入新kernel
|
||||
|
||||
./configure --with-kernel-support
|
||||
make install
|
||||
|
||||
想得到好的测量结果,请确保启用了本地APIC特性。如果opreport显示有0Hz CPU,
|
||||
说明APIC特性没有开启。另外注意idle=poll选项可能有损性能。
|
||||
|
||||
One time setup:
|
||||
opcontrol --setup --vmlinux=/boot/vmlinux
|
||||
|
||||
clear opcontrol --reset
|
||||
start opcontrol --start
|
||||
<test>
|
||||
stop opcontrol --stop
|
||||
dump output opreport > output_file
|
||||
|
||||
如果只看kernel相关的报告结果,请运行命令 opreport -l /boot/vmlinux > output_file
|
||||
|
||||
通过reset选项可以清理过期统计数据,相当于重启的效果。
|
||||
|
210
Documentation/zh_CN/email-clients.txt
Normal file
210
Documentation/zh_CN/email-clients.txt
Normal file
|
@ -0,0 +1,210 @@
|
|||
Chinese translated version of Documentation/email-clients.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: Harry Wei <harryxiyou@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/email-clients.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 贾威威 Harry Wei <harryxiyou@gmail.com>
|
||||
中文版翻译者: 贾威威 Harry Wei <harryxiyou@gmail.com>
|
||||
中文版校译者: Yinglin Luan <synmyth@gmail.com>
|
||||
Xiaochen Wang <wangxiaochen0@gmail.com>
|
||||
yaxinsn <yaxinsn@163.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Linux邮件客户端配置信息
|
||||
======================================================================
|
||||
|
||||
普通配置
|
||||
----------------------------------------------------------------------
|
||||
Linux内核补丁是通过邮件被提交的,最好把补丁作为邮件体的内嵌文本。有些维护者
|
||||
接收附件,但是附件的内容格式应该是"text/plain"。然而,附件一般是不赞成的,
|
||||
因为这会使补丁的引用部分在评论过程中变的很困难。
|
||||
|
||||
用来发送Linux内核补丁的邮件客户端在发送补丁时应该处于文本的原始状态。例如,
|
||||
他们不能改变或者删除制表符或者空格,甚至是在每一行的开头或者结尾。
|
||||
|
||||
不要通过"format=flowed"模式发送补丁。这样会引起不可预期以及有害的断行。
|
||||
|
||||
不要让你的邮件客户端进行自动换行。这样也会破坏你的补丁。
|
||||
|
||||
邮件客户端不能改变文本的字符集编码方式。要发送的补丁只能是ASCII或者UTF-8编码方式,
|
||||
如果你使用UTF-8编码方式发送邮件,那么你将会避免一些可能发生的字符集问题。
|
||||
|
||||
邮件客户端应该形成并且保持 References: 或者 In-Reply-To: 标题,那么
|
||||
邮件话题就不会中断。
|
||||
|
||||
复制粘帖(或者剪贴粘帖)通常不能用于补丁,因为制表符会转换为空格。使用xclipboard, xclip
|
||||
或者xcutsel也许可以,但是最好测试一下或者避免使用复制粘帖。
|
||||
|
||||
不要在使用PGP/GPG署名的邮件中包含补丁。这样会使得很多脚本不能读取和适用于你的补丁。
|
||||
(这个问题应该是可以修复的)
|
||||
|
||||
在给内核邮件列表发送补丁之前,给自己发送一个补丁是个不错的主意,保存接收到的
|
||||
邮件,将补丁用'patch'命令打上,如果成功了,再给内核邮件列表发送。
|
||||
|
||||
|
||||
一些邮件客户端提示
|
||||
----------------------------------------------------------------------
|
||||
这里给出一些详细的MUA配置提示,可以用于给Linux内核发送补丁。这些并不意味是
|
||||
所有的软件包配置总结。
|
||||
|
||||
说明:
|
||||
TUI = 以文本为基础的用户接口
|
||||
GUI = 图形界面用户接口
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Alpine (TUI)
|
||||
|
||||
配置选项:
|
||||
在"Sending Preferences"部分:
|
||||
|
||||
- "Do Not Send Flowed Text"必须开启
|
||||
- "Strip Whitespace Before Sending"必须关闭
|
||||
|
||||
当写邮件时,光标应该放在补丁会出现的地方,然后按下CTRL-R组合键,使指定的
|
||||
补丁文件嵌入到邮件中。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Evolution (GUI)
|
||||
|
||||
一些开发者成功的使用它发送补丁
|
||||
|
||||
当选择邮件选项:Preformat
|
||||
从Format->Heading->Preformatted (Ctrl-7)或者工具栏
|
||||
|
||||
然后使用:
|
||||
Insert->Text File... (Alt-n x)插入补丁文件。
|
||||
|
||||
你还可以"diff -Nru old.c new.c | xclip",选择Preformat,然后使用中间键进行粘帖。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Kmail (GUI)
|
||||
|
||||
一些开发者成功的使用它发送补丁。
|
||||
|
||||
默认设置不为HTML格式是合适的;不要启用它。
|
||||
|
||||
当书写一封邮件的时候,在选项下面不要选择自动换行。唯一的缺点就是你在邮件中输入的任何文本
|
||||
都不会被自动换行,因此你必须在发送补丁之前手动换行。最简单的方法就是启用自动换行来书写邮件,
|
||||
然后把它保存为草稿。一旦你在草稿中再次打开它,它已经全部自动换行了,那么你的邮件虽然没有
|
||||
选择自动换行,但是还不会失去已有的自动换行。
|
||||
|
||||
在邮件的底部,插入补丁之前,放上常用的补丁定界符:三个连字号(---)。
|
||||
|
||||
然后在"Message"菜单条目,选择插入文件,接着选取你的补丁文件。还有一个额外的选项,你可以
|
||||
通过它配置你的邮件建立工具栏菜单,还可以带上"insert file"图标。
|
||||
|
||||
你可以安全地通过GPG标记附件,但是内嵌补丁最好不要使用GPG标记它们。作为内嵌文本的签发补丁,
|
||||
当从GPG中提取7位编码时会使他们变的更加复杂。
|
||||
|
||||
如果你非要以附件的形式发送补丁,那么就右键点击附件,然后选中属性,突出"Suggest automatic
|
||||
display",这样内嵌附件更容易让读者看到。
|
||||
|
||||
当你要保存将要发送的内嵌文本补丁,你可以从消息列表窗格选择包含补丁的邮件,然后右击选择
|
||||
"save as"。你可以使用一个没有更改的包含补丁的邮件,如果它是以正确的形式组成。当你正真在它
|
||||
自己的窗口之下察看,那时没有选项可以保存邮件--已经有一个这样的bug被汇报到了kmail的bugzilla
|
||||
并且希望这将会被处理。邮件是以只针对某个用户可读写的权限被保存的,所以如果你想把邮件复制到其他地方,
|
||||
你不得不把他们的权限改为组或者整体可读。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Lotus Notes (GUI)
|
||||
|
||||
不要使用它。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Mutt (TUI)
|
||||
|
||||
很多Linux开发人员使用mutt客户端,所以证明它肯定工作的非常漂亮。
|
||||
|
||||
Mutt不自带编辑器,所以不管你使用什么编辑器都不应该带有自动断行。大多数编辑器都带有
|
||||
一个"insert file"选项,它可以通过不改变文件内容的方式插入文件。
|
||||
|
||||
'vim'作为mutt的编辑器:
|
||||
set editor="vi"
|
||||
|
||||
如果使用xclip,敲入以下命令
|
||||
:set paste
|
||||
按中键之前或者shift-insert或者使用
|
||||
:r filename
|
||||
|
||||
如果想要把补丁作为内嵌文本。
|
||||
(a)ttach工作的很好,不带有"set paste"。
|
||||
|
||||
配置选项:
|
||||
它应该以默认设置的形式工作。
|
||||
然而,把"send_charset"设置为"us-ascii::utf-8"也是一个不错的主意。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Pine (TUI)
|
||||
|
||||
Pine过去有一些空格删减问题,但是这些现在应该都被修复了。
|
||||
|
||||
如果可以,请使用alpine(pine的继承者)
|
||||
|
||||
配置选项:
|
||||
- 最近的版本需要消除流程文本
|
||||
- "no-strip-whitespace-before-send"选项也是需要的。
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sylpheed (GUI)
|
||||
|
||||
- 内嵌文本可以很好的工作(或者使用附件)。
|
||||
- 允许使用外部的编辑器。
|
||||
- 对于目录较多时非常慢。
|
||||
- 如果通过non-SSL连接,无法使用TLS SMTP授权。
|
||||
- 在组成窗口中有一个很有用的ruler bar。
|
||||
- 给地址本中添加地址就不会正确的了解显示名。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Thunderbird (GUI)
|
||||
|
||||
默认情况下,thunderbird很容易损坏文本,但是还有一些方法可以强制它变得更好。
|
||||
|
||||
- 在用户帐号设置里,组成和寻址,不要选择"Compose messages in HTML format"。
|
||||
|
||||
- 编辑你的Thunderbird配置设置来使它不要拆行使用:user_pref("mailnews.wraplength", 0);
|
||||
|
||||
- 编辑你的Thunderbird配置设置,使它不要使用"format=flowed"格式:user_pref("mailnews.
|
||||
send_plaintext_flowed", false);
|
||||
|
||||
- 你需要使Thunderbird变为预先格式方式:
|
||||
如果默认情况下你书写的是HTML格式,那不是很难。仅仅从标题栏的下拉框中选择"Preformat"格式。
|
||||
如果默认情况下你书写的是文本格式,你不得把它改为HTML格式(仅仅作为一次性的)来书写新的消息,
|
||||
然后强制使它回到文本格式,否则它就会拆行。要实现它,在写信的图标上使用shift键来使它变为HTML
|
||||
格式,然后标题栏的下拉框中选择"Preformat"格式。
|
||||
|
||||
- 允许使用外部的编辑器:
|
||||
针对Thunderbird打补丁最简单的方法就是使用一个"external editor"扩展,然后使用你最喜欢的
|
||||
$EDITOR来读取或者合并补丁到文本中。要实现它,可以下载并且安装这个扩展,然后添加一个使用它的
|
||||
按键View->Toolbars->Customize...最后当你书写信息的时候仅仅点击它就可以了。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
TkRat (GUI)
|
||||
|
||||
可以使用它。使用"Insert file..."或者外部的编辑器。
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Gmail (Web GUI)
|
||||
|
||||
不要使用它发送补丁。
|
||||
|
||||
Gmail网页客户端自动地把制表符转换为空格。
|
||||
|
||||
虽然制表符转换为空格问题可以被外部编辑器解决,同时它还会使用回车换行把每行拆分为78个字符。
|
||||
|
||||
另一个问题是Gmail还会把任何不是ASCII的字符的信息改为base64编码。它把东西变的像欧洲人的名字。
|
||||
|
||||
###
|
372
Documentation/zh_CN/filesystems/sysfs.txt
Normal file
372
Documentation/zh_CN/filesystems/sysfs.txt
Normal file
|
@ -0,0 +1,372 @@
|
|||
Chinese translated version of Documentation/filesystems/sysfs.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Patrick Mochel <mochel@osdl.org>
|
||||
Mike Murphy <mamurph@cs.clemson.edu>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/filesystems/sysfs.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
英文版维护者: Patrick Mochel <mochel@osdl.org>
|
||||
Mike Murphy <mamurph@cs.clemson.edu>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
sysfs - 用于导出内核对象(kobject)的文件系统
|
||||
|
||||
Patrick Mochel <mochel@osdl.org>
|
||||
Mike Murphy <mamurph@cs.clemson.edu>
|
||||
|
||||
修订: 16 August 2011
|
||||
原始版本: 10 January 2003
|
||||
|
||||
|
||||
sysfs 简介:
|
||||
~~~~~~~~~~
|
||||
|
||||
sysfs 是一个最初基于 ramfs 且位于内存的文件系统。它提供导出内核
|
||||
数据结构及其属性,以及它们之间的关联到用户空间的方法。
|
||||
|
||||
sysfs 始终与 kobject 的底层结构紧密相关。请阅读
|
||||
Documentation/kobject.txt 文档以获得更多关于 kobject 接口的
|
||||
信息。
|
||||
|
||||
|
||||
使用 sysfs
|
||||
~~~~~~~~~~~
|
||||
|
||||
只要内核配置中定义了 CONFIG_SYSFS ,sysfs 总是被编译进内核。你可
|
||||
通过以下命令挂载它:
|
||||
|
||||
mount -t sysfs sysfs /sys
|
||||
|
||||
|
||||
创建目录
|
||||
~~~~~~~~
|
||||
|
||||
任何 kobject 在系统中注册,就会有一个目录在 sysfs 中被创建。这个
|
||||
目录是作为该 kobject 的父对象所在目录的子目录创建的,以准确地传递
|
||||
内核的对象层次到用户空间。sysfs 中的顶层目录代表着内核对象层次的
|
||||
共同祖先;例如:某些对象属于某个子系统。
|
||||
|
||||
Sysfs 在与其目录关联的 sysfs_dirent 对象中内部保存一个指向实现
|
||||
目录的 kobject 的指针。以前,这个 kobject 指针被 sysfs 直接用于
|
||||
kobject 文件打开和关闭的引用计数。而现在的 sysfs 实现中,kobject
|
||||
引用计数只能通过 sysfs_schedule_callback() 函数直接修改。
|
||||
|
||||
|
||||
属性
|
||||
~~~~
|
||||
|
||||
kobject 的属性可在文件系统中以普通文件的形式导出。Sysfs 为属性定义
|
||||
了面向文件 I/O 操作的方法,以提供对内核属性的读写。
|
||||
|
||||
|
||||
属性应为 ASCII 码文本文件。以一个文件只存储一个属性值为宜。但一个
|
||||
文件只包含一个属性值可能影响效率,所以一个包含相同数据类型的属性值
|
||||
数组也被广泛地接受。
|
||||
|
||||
混合类型、表达多行数据以及一些怪异的数据格式会遭到强烈反对。这样做是
|
||||
很丢脸的,而且其代码会在未通知作者的情况下被重写。
|
||||
|
||||
|
||||
一个简单的属性结构定义如下:
|
||||
|
||||
struct attribute {
|
||||
char * name;
|
||||
struct module *owner;
|
||||
umode_t mode;
|
||||
};
|
||||
|
||||
|
||||
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
|
||||
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);
|
||||
|
||||
|
||||
一个单独的属性结构并不包含读写其属性值的方法。子系统最好为增删特定
|
||||
对象类型的属性定义自己的属性结构体和封装函数。
|
||||
|
||||
例如:驱动程序模型定义的 device_attribute 结构体如下:
|
||||
|
||||
struct device_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
|
||||
char *buf);
|
||||
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
};
|
||||
|
||||
int device_create_file(struct device *, const struct device_attribute *);
|
||||
void device_remove_file(struct device *, const struct device_attribute *);
|
||||
|
||||
为了定义设备属性,同时定义了一下辅助宏:
|
||||
|
||||
#define DEVICE_ATTR(_name, _mode, _show, _store) \
|
||||
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
|
||||
|
||||
例如:声明
|
||||
|
||||
static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);
|
||||
|
||||
等同于如下代码:
|
||||
|
||||
static struct device_attribute dev_attr_foo = {
|
||||
.attr = {
|
||||
.name = "foo",
|
||||
.mode = S_IWUSR | S_IRUGO,
|
||||
.show = show_foo,
|
||||
.store = store_foo,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
子系统特有的回调函数
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
当一个子系统定义一个新的属性类型时,必须实现一系列的 sysfs 操作,
|
||||
以帮助读写调用实现属性所有者的显示和储存方法。
|
||||
|
||||
struct sysfs_ops {
|
||||
ssize_t (*show)(struct kobject *, struct attribute *, char *);
|
||||
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
|
||||
};
|
||||
|
||||
[子系统应已经定义了一个 struct kobj_type 结构体作为这个类型的
|
||||
描述符,并在此保存 sysfs_ops 的指针。更多的信息参见 kobject 的
|
||||
文档]
|
||||
|
||||
sysfs 会为这个类型调用适当的方法。当一个文件被读写时,这个方法会
|
||||
将一般的kobject 和 attribute 结构体指针转换为适当的指针类型后
|
||||
调用相关联的函数。
|
||||
|
||||
|
||||
示例:
|
||||
|
||||
#define to_dev(obj) container_of(obj, struct device, kobj)
|
||||
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
|
||||
|
||||
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct device_attribute *dev_attr = to_dev_attr(attr);
|
||||
struct device *dev = to_dev(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (dev_attr->show)
|
||||
ret = dev_attr->show(dev, dev_attr, buf);
|
||||
if (ret >= (ssize_t)PAGE_SIZE) {
|
||||
print_symbol("dev_attr_show: %s returned bad count\n",
|
||||
(unsigned long)dev_attr->show);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
读写属性数据
|
||||
~~~~~~~~~~~~
|
||||
|
||||
在声明属性时,必须指定 show() 或 store() 方法,以实现属性的
|
||||
读或写。这些方法的类型应该和以下的设备属性定义一样简单。
|
||||
|
||||
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
|
||||
也就是说,他们应只以一个处理对象、一个属性和一个缓冲指针作为参数。
|
||||
|
||||
sysfs 会分配一个大小为 (PAGE_SIZE) 的缓冲区并传递给这个方法。
|
||||
Sysfs 将会为每次读写操作调用一次这个方法。这使得这些方法在执行时
|
||||
会出现以下的行为:
|
||||
|
||||
- 在读方面(read(2)),show() 方法应该填充整个缓冲区。回想属性
|
||||
应只导出了一个属性值或是一个同类型属性值的数组,所以这个代价将
|
||||
不会不太高。
|
||||
|
||||
这使得用户空间可以局部地读和任意的向前搜索整个文件。如果用户空间
|
||||
向后搜索到零或使用‘0’偏移执行一个pread(2)操作,show()方法将
|
||||
再次被调用,以重新填充缓存。
|
||||
|
||||
- 在写方面(write(2)),sysfs 希望在第一次写操作时得到整个缓冲区。
|
||||
之后 Sysfs 传递整个缓冲区给 store() 方法。
|
||||
|
||||
当要写 sysfs 文件时,用户空间进程应首先读取整个文件,修该想要
|
||||
改变的值,然后回写整个缓冲区。
|
||||
|
||||
在读写属性值时,属性方法的执行应操作相同的缓冲区。
|
||||
|
||||
注记:
|
||||
|
||||
- 写操作导致的 show() 方法重载,会忽略当前文件位置。
|
||||
|
||||
- 缓冲区应总是 PAGE_SIZE 大小。对于i386,这个值为4096。
|
||||
|
||||
- show() 方法应该返回写入缓冲区的字节数,也就是 snprintf()的
|
||||
返回值。
|
||||
|
||||
- show() 应始终使用 snprintf()。
|
||||
|
||||
- store() 应返回缓冲区的已用字节数。如果整个缓存都已填满,只需返回
|
||||
count 参数。
|
||||
|
||||
- show() 或 store() 可以返回错误值。当得到一个非法值,必须返回一个
|
||||
错误值。
|
||||
|
||||
- 一个传递给方法的对象将会通过 sysfs 调用对象内嵌的引用计数固定在
|
||||
内存中。尽管如此,对象代表的物理实体(如设备)可能已不存在。如有必要,
|
||||
应该实现一个检测机制。
|
||||
|
||||
一个简单的(未经实验证实的)设备属性实现如下:
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
|
||||
}
|
||||
|
||||
static ssize_t store_name(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
snprintf(dev->name, sizeof(dev->name), "%.*s",
|
||||
(int)min(count, sizeof(dev->name) - 1), buf);
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);
|
||||
|
||||
|
||||
(注意:真正的实现不允许用户空间设置设备名。)
|
||||
|
||||
顶层目录布局
|
||||
~~~~~~~~~~~~
|
||||
|
||||
sysfs 目录的安排显示了内核数据结构之间的关系。
|
||||
|
||||
顶层 sysfs 目录如下:
|
||||
|
||||
block/
|
||||
bus/
|
||||
class/
|
||||
dev/
|
||||
devices/
|
||||
firmware/
|
||||
net/
|
||||
fs/
|
||||
|
||||
devices/ 包含了一个设备树的文件系统表示。他直接映射了内部的内核
|
||||
设备树,反映了设备的层次结构。
|
||||
|
||||
bus/ 包含了内核中各种总线类型的平面目录布局。每个总线目录包含两个
|
||||
子目录:
|
||||
|
||||
devices/
|
||||
drivers/
|
||||
|
||||
devices/ 包含了系统中出现的每个设备的符号链接,他们指向 root/ 下的
|
||||
设备目录。
|
||||
|
||||
drivers/ 包含了每个已为特定总线上的设备而挂载的驱动程序的目录(这里
|
||||
假定驱动没有跨越多个总线类型)。
|
||||
|
||||
fs/ 包含了一个为文件系统设立的目录。现在每个想要导出属性的文件系统必须
|
||||
在 fs/ 下创建自己的层次结构(参见Documentation/filesystems/fuse.txt)。
|
||||
|
||||
dev/ 包含两个子目录: char/ 和 block/。在这两个子目录中,有以
|
||||
<major>:<minor> 格式命名的符号链接。这些符号链接指向 sysfs 目录
|
||||
中相应的设备。/sys/dev 提供一个通过一个 stat(2) 操作结果,查找
|
||||
设备 sysfs 接口快捷的方法。
|
||||
|
||||
更多有关 driver-model 的特性信息可以在 Documentation/driver-model/
|
||||
中找到。
|
||||
|
||||
|
||||
TODO: 完成这一节。
|
||||
|
||||
|
||||
当前接口
|
||||
~~~~~~~~
|
||||
|
||||
以下的接口层普遍存在于当前的sysfs中:
|
||||
|
||||
- 设备 (include/linux/device.h)
|
||||
----------------------------------
|
||||
结构体:
|
||||
|
||||
struct device_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
|
||||
char *buf);
|
||||
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
};
|
||||
|
||||
声明:
|
||||
|
||||
DEVICE_ATTR(_name, _mode, _show, _store);
|
||||
|
||||
增/删属性:
|
||||
|
||||
int device_create_file(struct device *dev, const struct device_attribute * attr);
|
||||
void device_remove_file(struct device *dev, const struct device_attribute * attr);
|
||||
|
||||
|
||||
- 总线驱动程序 (include/linux/device.h)
|
||||
--------------------------------------
|
||||
结构体:
|
||||
|
||||
struct bus_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct bus_type *, char * buf);
|
||||
ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
|
||||
};
|
||||
|
||||
声明:
|
||||
|
||||
BUS_ATTR(_name, _mode, _show, _store)
|
||||
|
||||
增/删属性:
|
||||
|
||||
int bus_create_file(struct bus_type *, struct bus_attribute *);
|
||||
void bus_remove_file(struct bus_type *, struct bus_attribute *);
|
||||
|
||||
|
||||
- 设备驱动程序 (include/linux/device.h)
|
||||
-----------------------------------------
|
||||
|
||||
结构体:
|
||||
|
||||
struct driver_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct device_driver *, char * buf);
|
||||
ssize_t (*store)(struct device_driver *, const char * buf,
|
||||
size_t count);
|
||||
};
|
||||
|
||||
声明:
|
||||
|
||||
DRIVER_ATTR(_name, _mode, _show, _store)
|
||||
|
||||
增/删属性:
|
||||
|
||||
int driver_create_file(struct device_driver *, const struct driver_attribute *);
|
||||
void driver_remove_file(struct device_driver *, const struct driver_attribute *);
|
||||
|
||||
|
||||
文档
|
||||
~~~~
|
||||
|
||||
sysfs 目录结构以及其中包含的属性定义了一个内核与用户空间之间的 ABI。
|
||||
对于任何 ABI,其自身的稳定和适当的文档是非常重要的。所有新的 sysfs
|
||||
属性必须在 Documentation/ABI 中有文档。详见 Documentation/ABI/README。
|
658
Documentation/zh_CN/gpio.txt
Normal file
658
Documentation/zh_CN/gpio.txt
Normal file
|
@ -0,0 +1,658 @@
|
|||
Chinese translated version of Documentation/gpio.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Grant Likely <grant.likely@secretlab.ca>
|
||||
Linus Walleij <linus.walleij@linaro.org>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/gpio.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
英文版维护者: Grant Likely <grant.likely@secretlab.ca>
|
||||
Linus Walleij <linus.walleij@linaro.org>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
GPIO 接口
|
||||
|
||||
本文档提供了一个在Linux下访问GPIO的公约概述。
|
||||
|
||||
这些函数以 gpio_* 作为前缀。其他的函数不允许使用这样的前缀或相关的
|
||||
__gpio_* 前缀。
|
||||
|
||||
|
||||
什么是GPIO?
|
||||
==========
|
||||
"通用输入/输出口"(GPIO)是一个灵活的由软件控制的数字信号。他们可
|
||||
由多种芯片提供,且对于从事嵌入式和定制硬件的 Linux 开发者来说是
|
||||
比较熟悉。每个GPIO 都代表一个连接到特定引脚或球栅阵列(BGA)封装中
|
||||
“球珠”的一个位。电路板原理图显示了 GPIO 与外部硬件的连接关系。
|
||||
驱动可以编写成通用代码,以使板级启动代码可传递引脚配置数据给驱动。
|
||||
|
||||
片上系统 (SOC) 处理器对 GPIO 有很大的依赖。在某些情况下,每个
|
||||
非专用引脚都可配置为 GPIO,且大多数芯片都最少有一些 GPIO。
|
||||
可编程逻辑器件(类似 FPGA) 可以方便地提供 GPIO。像电源管理和
|
||||
音频编解码器这样的多功能芯片经常留有一些这样的引脚来帮助那些引脚
|
||||
匮乏的 SOC。同时还有通过 I2C 或 SPI 串行总线连接的“GPIO扩展器”
|
||||
芯片。大多数 PC 的南桥有一些拥有 GPIO 能力的引脚 (只有BIOS
|
||||
固件才知道如何使用他们)。
|
||||
|
||||
GPIO 的实际功能因系统而异。通常用法有:
|
||||
|
||||
- 输出值可写 (高电平=1,低电平=0)。一些芯片也有如何驱动这些值的选项,
|
||||
例如只允许输出一个值、支持“线与”及其他取值类似的模式(值得注意的是
|
||||
“开漏”信号)
|
||||
|
||||
- 输入值可读(1、0)。一些芯片支持引脚在配置为“输出”时回读,这对于类似
|
||||
“线与”的情况(以支持双向信号)是非常有用的。GPIO 控制器可能有输入
|
||||
去毛刺/消抖逻辑,这有时需要软件控制。
|
||||
|
||||
- 输入通常可作为 IRQ 信号,一般是沿触发,但有时是电平触发。这样的 IRQ
|
||||
可能配置为系统唤醒事件,以将系统从低功耗状态下唤醒。
|
||||
|
||||
- 通常一个 GPIO 根据不同产品电路板的需求,可以配置为输入或输出,也有仅
|
||||
支持单向的。
|
||||
|
||||
- 大部分 GPIO 可以在持有自旋锁时访问,但是通常由串行总线扩展的 GPIO
|
||||
不允许持有自旋锁。但某些系统也支持这种类型。
|
||||
|
||||
对于给定的电路板,每个 GPIO 都用于某个特定的目的,如监控 MMC/SD 卡的
|
||||
插入/移除、检测卡的写保护状态、驱动 LED、配置收发器、模拟串行总线、
|
||||
复位硬件看门狗、感知开关状态等等。
|
||||
|
||||
|
||||
GPIO 公约
|
||||
=========
|
||||
注意,这个叫做“公约”,因为这不是强制性的,不遵循这个公约是无伤大雅的,
|
||||
因为此时可移植性并不重要。GPIO 常用于板级特定的电路逻辑,甚至可能
|
||||
随着电路板的版本而改变,且不可能在不同走线的电路板上使用。仅有在少数
|
||||
功能上才具有可移植性,其他功能是平台特定。这也是由于“胶合”的逻辑造成的。
|
||||
|
||||
此外,这不需要任何的执行框架,只是一个接口。某个平台可能通过一个简单地
|
||||
访问芯片寄存器的内联函数来实现它,其他平台可能通过委托一系列不同的GPIO
|
||||
控制器的抽象函数来实现它。(有一些可选的代码能支持这种策略的实现,本文档
|
||||
后面会介绍,但作为 GPIO 接口的客户端驱动程序必须与它的实现无关。)
|
||||
|
||||
也就是说,如果在他们的平台上支持这个公约,驱动应尽可能的使用它。同时,平台
|
||||
必须在 Kconfig 中选择 ARCH_REQUIRE_GPIOLIB 或者 ARCH_WANT_OPTIONAL_GPIOLIB
|
||||
选项。那些调用标准 GPIO 函数的驱动应该在 Kconfig 入口中声明依赖GENERIC_GPIO。
|
||||
当驱动包含文件:
|
||||
|
||||
#include <linux/gpio.h>
|
||||
|
||||
则 GPIO 函数是可用,无论是“真实代码”还是经优化过的语句。如果你遵守
|
||||
这个公约,当你的代码完成后,对其他的开发者来说会更容易看懂和维护。
|
||||
|
||||
注意,这些操作包含所用平台的 I/O 屏障代码,驱动无须显式地调用他们。
|
||||
|
||||
|
||||
标识 GPIO
|
||||
---------
|
||||
GPIO 是通过无符号整型来标识的,范围是 0 到 MAX_INT。保留“负”数
|
||||
用于其他目的,例如标识信号“在这个板子上不可用”或指示错误。未接触底层
|
||||
硬件的代码会忽略这些整数。
|
||||
|
||||
平台会定义这些整数的用法,且通常使用 #define 来定义 GPIO,这样
|
||||
板级特定的启动代码可以直接关联相应的原理图。相对来说,驱动应该仅使用
|
||||
启动代码传递过来的 GPIO 编号,使用 platform_data 保存板级特定
|
||||
引脚配置数据 (同时还有其他须要的板级特定数据),避免可能出现的问题。
|
||||
|
||||
例如一个平台使用编号 32-159 来标识 GPIO,而在另一个平台使用编号0-63
|
||||
标识一组 GPIO 控制器,64-79标识另一类 GPIO 控制器,且在一个含有
|
||||
FPGA 的特定板子上使用 80-95。编号不一定要连续,那些平台中,也可以
|
||||
使用编号2000-2063来标识一个 I2C 接口的 GPIO 扩展器中的 GPIO。
|
||||
|
||||
如果你要初始化一个带有无效 GPIO 编号的结构体,可以使用一些负编码
|
||||
(如"-EINVAL"),那将使其永远不会是有效。来测试这样一个结构体中的编号
|
||||
是否关联一个 GPIO,你可使用以下断言:
|
||||
|
||||
int gpio_is_valid(int number);
|
||||
|
||||
如果编号不存在,则请求和释放 GPIO 的函数将拒绝执行相关操作(见下文)。
|
||||
其他编号也可能被拒绝,比如一个编号可能存在,但暂时在给定的电路上不可用。
|
||||
|
||||
一个平台是否支持多个 GPIO 控制器为平台特定的实现问题,就像是否可以
|
||||
在 GPIO 编号空间中有“空洞”和是否可以在运行时添加新的控制器一样。
|
||||
这些问题会影响其他事情,包括相邻的 GPIO 编号是否存在等。
|
||||
|
||||
使用 GPIO
|
||||
---------
|
||||
对于一个 GPIO,系统应该做的第一件事情就是通过 gpio_request()
|
||||
函数分配它,见下文。
|
||||
|
||||
接下来是设置I/O方向,这通常是在板级启动代码中为所使用的 GPIO 设置
|
||||
platform_device 时完成。
|
||||
|
||||
/* 设置为输入或输出, 返回 0 或负的错误代码 */
|
||||
int gpio_direction_input(unsigned gpio);
|
||||
int gpio_direction_output(unsigned gpio, int value);
|
||||
|
||||
返回值为零代表成功,否则返回一个负的错误代码。这个返回值需要检查,因为
|
||||
get/set(获取/设置)函数调用没法返回错误,且有可能是配置错误。通常,
|
||||
你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO,在板子
|
||||
启动的早期、进程启动前使用他们也是可以的。
|
||||
|
||||
对于作为输出的 GPIO,为其提供初始输出值,对于避免在系统启动期间出现
|
||||
信号毛刺是很有帮助的。
|
||||
|
||||
为了与传统的 GPIO 接口兼容, 在设置一个 GPIO 方向时,如果它还未被申请,
|
||||
则隐含了申请那个 GPIO 的操作(见下文)。这种兼容性正在从可选的 gpiolib
|
||||
框架中移除。
|
||||
|
||||
如果这个 GPIO 编码不存在,或者特定的 GPIO 不能用于那种模式,则方向
|
||||
设置可能失败。依赖启动固件来正确地设置方向通常是一个坏主意,因为它可能
|
||||
除了启动Linux,并没有做更多的验证工作。(同理, 板子的启动代码可能需要
|
||||
将这个复用的引脚设置为 GPIO,并正确地配置上拉/下拉电阻。)
|
||||
|
||||
|
||||
访问自旋锁安全的 GPIO
|
||||
-------------------
|
||||
大多数 GPIO 控制器可以通过内存读/写指令来访问。这些指令不会休眠,可以
|
||||
安全地在硬(非线程)中断例程和类似的上下文中完成。
|
||||
|
||||
对于那些用 gpio_cansleep()测试总是返回失败的 GPIO(见下文),使用
|
||||
以下的函数访问:
|
||||
|
||||
/* GPIO 输入:返回零或非零 */
|
||||
int gpio_get_value(unsigned gpio);
|
||||
|
||||
/* GPIO 输出 */
|
||||
void gpio_set_value(unsigned gpio, int value);
|
||||
|
||||
GPIO值是布尔值,零表示低电平,非零表示高电平。当读取一个输出引脚的值时,
|
||||
返回值应该是引脚上的值。这个值不总是和输出值相符,因为存在开漏输出信号和
|
||||
输出延迟问题。
|
||||
|
||||
以上的 get/set 函数无错误返回值,因为之前 gpio_direction_*()应已检查过
|
||||
其是否为“无效GPIO”。此外,还需要注意的是并不是所有平台都可以从输出引脚
|
||||
中读取数据,对于不能读取的引脚应总返回零。另外,对那些在原子上下文中无法
|
||||
安全访问的 GPIO (译者注:因为访问可能导致休眠)使用这些函数是不合适的
|
||||
(见下文)。
|
||||
|
||||
在 GPIO 编号(还有输出、值)为常数的情况下,鼓励通过平台特定的实现来优化
|
||||
这两个函数来访问 GPIO 值。这种情况(读写一个硬件寄存器)下只需要几条指令
|
||||
是很正常的,且无须自旋锁。这种优化函数比起那些在子程序上花费许多指令的
|
||||
函数可以使得模拟接口(译者注:例如 GPIO 模拟 I2C、1-wire 或 SPI)的
|
||||
应用(在空间和时间上都)更具效率。
|
||||
|
||||
|
||||
访问可能休眠的 GPIO
|
||||
-----------------
|
||||
某些 GPIO 控制器必须通过基于总线(如 I2C 或 SPI)的消息访问。读或写这些
|
||||
GPIO 值的命令需要等待其信息排到队首才发送命令,再获得其反馈。期间需要
|
||||
休眠,这不能在 IRQ 例程(中断上下文)中执行。
|
||||
|
||||
支持此类 GPIO 的平台通过以下函数返回非零值来区分出这种 GPIO。(此函数需要
|
||||
一个之前通过 gpio_request 分配到的有效 GPIO 编号):
|
||||
|
||||
int gpio_cansleep(unsigned gpio);
|
||||
|
||||
为了访问这种 GPIO,内核定义了一套不同的函数:
|
||||
|
||||
/* GPIO 输入:返回零或非零 ,可能会休眠 */
|
||||
int gpio_get_value_cansleep(unsigned gpio);
|
||||
|
||||
/* GPIO 输出,可能会休眠 */
|
||||
void gpio_set_value_cansleep(unsigned gpio, int value);
|
||||
|
||||
|
||||
访问这样的 GPIO 需要一个允许休眠的上下文,例如线程 IRQ 处理例程,并用以上的
|
||||
访问函数替换那些没有 cansleep()后缀的自旋锁安全访问函数。
|
||||
|
||||
除了这些访问函数可能休眠,且它们操作的 GPIO 不能在硬件 IRQ 处理例程中访问的
|
||||
事实,这些处理例程实际上和自旋锁安全的函数是一样的。
|
||||
|
||||
** 除此之外 ** 调用设置和配置此类 GPIO 的函数也必须在允许休眠的上下文中,
|
||||
因为它们可能也需要访问 GPIO 控制器芯片: (这些设置函数通常在板级启动代码或者
|
||||
驱动探测/断开代码中,所以这是一个容易满足的约束条件。)
|
||||
|
||||
gpio_direction_input()
|
||||
gpio_direction_output()
|
||||
gpio_request()
|
||||
|
||||
## gpio_request_one()
|
||||
## gpio_request_array()
|
||||
## gpio_free_array()
|
||||
|
||||
gpio_free()
|
||||
gpio_set_debounce()
|
||||
|
||||
|
||||
|
||||
声明和释放 GPIO
|
||||
----------------------------
|
||||
为了有助于捕获系统配置错误,定义了两个函数。
|
||||
|
||||
/* 申请 GPIO, 返回 0 或负的错误代码.
|
||||
* 非空标签可能有助于诊断.
|
||||
*/
|
||||
int gpio_request(unsigned gpio, const char *label);
|
||||
|
||||
/* 释放之前声明的 GPIO */
|
||||
void gpio_free(unsigned gpio);
|
||||
|
||||
将无效的 GPIO 编码传递给 gpio_request()会导致失败,申请一个已使用这个
|
||||
函数声明过的 GPIO 也会失败。gpio_request()的返回值必须检查。你应该在
|
||||
进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO,在板子启动的早期、
|
||||
进入进程之前是可以申请的。
|
||||
|
||||
这个函数完成两个基本的目标。一是标识那些实际上已作为 GPIO 使用的信号线,
|
||||
这样便于更好地诊断;系统可能需要服务几百个可用的 GPIO,但是对于任何一个
|
||||
给定的电路板通常只有一些被使用。另一个目的是捕获冲突,查明错误:如两个或
|
||||
更多驱动错误地认为他们已经独占了某个信号线,或是错误地认为移除一个管理着
|
||||
某个已激活信号的驱动是安全的。也就是说,申请 GPIO 的作用类似一种锁机制。
|
||||
|
||||
某些平台可能也使用 GPIO 作为电源管理激活信号(例如通过关闭未使用芯片区和
|
||||
简单地关闭未使用时钟)。
|
||||
|
||||
对于 GPIO 使用 pinctrl 子系统已知的引脚,子系统应该被告知其使用情况;
|
||||
一个 gpiolib 驱动的 .request()操作应调用 pinctrl_request_gpio(),
|
||||
而 gpiolib 驱动的 .free()操作应调用 pinctrl_free_gpio()。pinctrl
|
||||
子系统允许 pinctrl_request_gpio()在某个引脚或引脚组以复用形式“属于”
|
||||
一个设备时都成功返回。
|
||||
|
||||
任何须将 GPIO 信号导向适当引脚的引脚复用硬件的编程应该发生在 GPIO
|
||||
驱动的 .direction_input()或 .direction_output()函数中,以及
|
||||
任何输出 GPIO 值的设置之后。这样可使从引脚特殊功能到 GPIO 的转换
|
||||
不会在引脚产生毛刺波形。有时当用一个 GPIO 实现其信号驱动一个非 GPIO
|
||||
硬件模块的解决方案时,就需要这种机制。
|
||||
|
||||
某些平台允许部分或所有 GPIO 信号使用不同的引脚。类似的,GPIO 或引脚的
|
||||
其他方面也需要配置,如上拉/下拉。平台软件应该在对这些 GPIO 调用
|
||||
gpio_request()前将这类细节配置好,例如使用 pinctrl 子系统的映射表,
|
||||
使得 GPIO 的用户无须关注这些细节。
|
||||
|
||||
还有一个值得注意的是在释放 GPIO 前,你必须停止使用它。
|
||||
|
||||
|
||||
注意:申请一个 GPIO 并没有以任何方式配置它,只不过标识那个 GPIO 处于使用
|
||||
状态。必须有另外的代码来处理引脚配置(如控制 GPIO 使用的引脚、上拉/下拉)。
|
||||
考虑到大多数情况下声明 GPIO 之后就会立即配置它们,所以定义了以下三个辅助函数:
|
||||
|
||||
/* 申请一个 GPIO 信号, 同时通过特定的'flags'初始化配置,
|
||||
* 其他和 gpio_request()的参数和返回值相同
|
||||
*
|
||||
*/
|
||||
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
|
||||
|
||||
/* 在单个函数中申请多个 GPIO
|
||||
*/
|
||||
int gpio_request_array(struct gpio *array, size_t num);
|
||||
|
||||
/* 在单个函数中释放多个 GPIO
|
||||
*/
|
||||
void gpio_free_array(struct gpio *array, size_t num);
|
||||
|
||||
这里 'flags' 当前定义可指定以下属性:
|
||||
|
||||
* GPIOF_DIR_IN - 配置方向为输入
|
||||
* GPIOF_DIR_OUT - 配置方向为输出
|
||||
|
||||
* GPIOF_INIT_LOW - 在作为输出时,初始值为低电平
|
||||
* GPIOF_INIT_HIGH - 在作为输出时,初始值为高电平
|
||||
* GPIOF_OPEN_DRAIN - gpio引脚为开漏信号
|
||||
* GPIOF_OPEN_SOURCE - gpio引脚为源极开路信号
|
||||
|
||||
* GPIOF_EXPORT_DIR_FIXED - 将 gpio 导出到 sysfs,并保持方向
|
||||
* GPIOF_EXPORT_DIR_CHANGEABLE - 同样是导出, 但允许改变方向
|
||||
|
||||
因为 GPIOF_INIT_* 仅有在配置为输出的时候才存在,所以有效的组合为:
|
||||
|
||||
* GPIOF_IN - 配置为输入
|
||||
* GPIOF_OUT_INIT_LOW - 配置为输出,并初始化为低电平
|
||||
* GPIOF_OUT_INIT_HIGH - 配置为输出,并初始化为高电平
|
||||
|
||||
当设置 flag 为 GPIOF_OPEN_DRAIN 时,则假设引脚是开漏信号。这样的引脚
|
||||
将不会在输出模式下置1。这样的引脚需要连接上拉电阻。通过使能这个标志,gpio库
|
||||
将会在被要求输出模式下置1时将引脚变为输入状态来使引脚置高。引脚在输出模式下
|
||||
通过置0使其输出低电平。
|
||||
|
||||
当设置 flag 为 GPIOF_OPEN_SOURCE 时,则假设引脚为源极开路信号。这样的引脚
|
||||
将不会在输出模式下置0。这样的引脚需要连接下拉电阻。通过使能这个标志,gpio库
|
||||
将会在被要求输出模式下置0时将引脚变为输入状态来使引脚置低。引脚在输出模式下
|
||||
通过置1使其输出高电平。
|
||||
|
||||
将来这些标志可能扩展到支持更多的属性。
|
||||
|
||||
更进一步,为了更简单地声明/释放多个 GPIO,'struct gpio'被引进来封装所有
|
||||
这三个领域:
|
||||
|
||||
struct gpio {
|
||||
unsigned gpio;
|
||||
unsigned long flags;
|
||||
const char *label;
|
||||
};
|
||||
|
||||
一个典型的用例:
|
||||
|
||||
static struct gpio leds_gpios[] = {
|
||||
{ 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* 默认开启 */
|
||||
{ 33, GPIOF_OUT_INIT_LOW, "Green LED" }, /* 默认关闭 */
|
||||
{ 34, GPIOF_OUT_INIT_LOW, "Red LED" }, /* 默认关闭 */
|
||||
{ 35, GPIOF_OUT_INIT_LOW, "Blue LED" }, /* 默认关闭 */
|
||||
{ ... },
|
||||
};
|
||||
|
||||
err = gpio_request_one(31, GPIOF_IN, "Reset Button");
|
||||
if (err)
|
||||
...
|
||||
|
||||
err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
|
||||
if (err)
|
||||
...
|
||||
|
||||
gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));
|
||||
|
||||
|
||||
GPIO 映射到 IRQ
|
||||
--------------------
|
||||
GPIO 编号是无符号整数;IRQ 编号也是。这些构成了两个逻辑上不同的命名空间
|
||||
(GPIO 0 不一定使用 IRQ 0)。你可以通过以下函数在它们之间实现映射:
|
||||
|
||||
/* 映射 GPIO 编号到 IRQ 编号 */
|
||||
int gpio_to_irq(unsigned gpio);
|
||||
|
||||
/* 映射 IRQ 编号到 GPIO 编号 (尽量避免使用) */
|
||||
int irq_to_gpio(unsigned irq);
|
||||
|
||||
它们的返回值为对应命名空间的相关编号,或是负的错误代码(如果无法映射)。
|
||||
(例如,某些 GPIO 无法做为 IRQ 使用。)以下的编号错误是未经检测的:使用一个
|
||||
未通过 gpio_direction_input()配置为输入的 GPIO 编号,或者使用一个
|
||||
并非来源于gpio_to_irq()的 IRQ 编号。
|
||||
|
||||
这两个映射函数可能会在信号编号的加减计算过程上花些时间。它们不可休眠。
|
||||
|
||||
gpio_to_irq()返回的非错误值可以传递给 request_irq()或者 free_irq()。
|
||||
它们通常通过板级特定的初始化代码存放到平台设备的 IRQ 资源中。注意:IRQ
|
||||
触发选项是 IRQ 接口的一部分,如 IRQF_TRIGGER_FALLING,系统唤醒能力
|
||||
也是如此。
|
||||
|
||||
irq_to_gpio()返回的非错误值大多数通常可以被 gpio_get_value()所使用,
|
||||
比如在 IRQ 是沿触发时初始化或更新驱动状态。注意某些平台不支持反映射,所以
|
||||
你应该尽量避免使用它。
|
||||
|
||||
|
||||
模拟开漏信号
|
||||
----------------------------
|
||||
有时在只有低电平信号作为实际驱动结果(译者注:多个输出连接于一点,逻辑电平
|
||||
结果为所有输出的逻辑与)的时候,共享的信号线需要使用“开漏”信号。(该术语
|
||||
适用于 CMOS 管;而 TTL 用“集电极开路”。)一个上拉电阻使信号为高电平。这
|
||||
有时被称为“线与”。实际上,从负逻辑(低电平为真)的角度来看,这是一个“线或”。
|
||||
|
||||
一个开漏信号的常见例子是共享的低电平使能 IRQ 信号线。此外,有时双向数据总线
|
||||
信号也使用漏极开路信号。
|
||||
|
||||
某些 GPIO 控制器直接支持开漏输出,还有许多不支持。当你需要开漏信号,但
|
||||
硬件又不直接支持的时候,一个常用的方法是用任何即可作输入也可作输出的 GPIO
|
||||
引脚来模拟:
|
||||
|
||||
LOW: gpio_direction_output(gpio, 0) ... 这代码驱动信号并覆盖
|
||||
上拉配置。
|
||||
|
||||
HIGH: gpio_direction_input(gpio) ... 这代码关闭输出,所以上拉电阻
|
||||
(或其他的一些器件)控制了信号。
|
||||
|
||||
如果你将信号线“驱动”为高电平,但是 gpio_get_value(gpio)报告了一个
|
||||
低电平(在适当的上升时间后),你就可以知道是其他的一些组件将共享信号线拉低了。
|
||||
这不一定是错误的。一个常见的例子就是 I2C 时钟的延长:一个需要较慢时钟的
|
||||
从设备延迟 SCK 的上升沿,而 I2C 主设备相应地调整其信号传输速率。
|
||||
|
||||
|
||||
这些公约忽略了什么?
|
||||
================
|
||||
这些公约忽略的最大一件事就是引脚复用,因为这属于高度芯片特定的属性且
|
||||
没有可移植性。某个平台可能不需要明确的复用信息;有的对于任意给定的引脚
|
||||
可能只有两个功能选项;有的可能每个引脚有八个功能选项;有的可能可以将
|
||||
几个引脚中的任何一个作为给定的 GPIO。(是的,这些例子都来自于当前运行
|
||||
Linux 的系统。)
|
||||
|
||||
在某些系统中,与引脚复用相关的是配置和使能集成的上、下拉模式。并不是所有
|
||||
平台都支持这种模式,或者不会以相同的方式来支持这种模式;且任何给定的电路板
|
||||
可能使用外置的上拉(或下拉)电阻,这时芯片上的就不应该使用。(当一个电路需要
|
||||
5kOhm 的拉动电阻,芯片上的 100 kOhm 电阻就不能做到。)同样的,驱动能力
|
||||
(2 mA vs 20 mA)和电压(1.8V vs 3.3V)是平台特定问题,就像模型一样在
|
||||
可配置引脚和 GPIO 之间(没)有一一对应的关系。
|
||||
|
||||
还有其他一些系统特定的机制没有在这里指出,例如上述的输入去毛刺和线与输出
|
||||
选项。硬件可能支持批量读或写 GPIO,但是那一般是配置相关的:对于处于同一
|
||||
块区(bank)的GPIO。(GPIO 通常以 16 或 32 个组成一个区块,一个给定的
|
||||
片上系统一般有几个这样的区块。)某些系统可以通过输出 GPIO 触发 IRQ,
|
||||
或者从并非以 GPIO 管理的引脚取值。这些机制的相关代码没有必要具有可移植性。
|
||||
|
||||
当前,动态定义 GPIO 并不是标准的,例如作为配置一个带有某些 GPIO 扩展器的
|
||||
附加电路板的副作用。
|
||||
|
||||
GPIO 实现者的框架 (可选)
|
||||
=====================
|
||||
前面提到了,有一个可选的实现框架,让平台使用相同的编程接口,更加简单地支持
|
||||
不同种类的 GPIO 控制器。这个框架称为"gpiolib"。
|
||||
|
||||
作为一个辅助调试功能,如果 debugfs 可用,就会有一个 /sys/kernel/debug/gpio
|
||||
文件。通过这个框架,它可以列出所有注册的控制器,以及当前正在使用中的 GPIO
|
||||
的状态。
|
||||
|
||||
|
||||
控制器驱动: gpio_chip
|
||||
-------------------
|
||||
在框架中每个 GPIO 控制器都包装为一个 "struct gpio_chip",他包含了
|
||||
该类型的每个控制器的常用信息:
|
||||
|
||||
- 设置 GPIO 方向的方法
|
||||
- 用于访问 GPIO 值的方法
|
||||
- 告知调用其方法是否可能休眠的标志
|
||||
- 可选的 debugfs 信息导出方法 (显示类似上拉配置一样的额外状态)
|
||||
- 诊断标签
|
||||
|
||||
也包含了来自 device.platform_data 的每个实例的数据:它第一个 GPIO 的
|
||||
编号和它可用的 GPIO 的数量。
|
||||
|
||||
实现 gpio_chip 的代码应支持多控制器实例,这可能使用驱动模型。那些代码要
|
||||
配置每个 gpio_chip,并发起gpiochip_add()。卸载一个 GPIO 控制器很少见,
|
||||
但在必要的时候可以使用 gpiochip_remove()。
|
||||
|
||||
大部分 gpio_chip 是一个实例特定结构体的一部分,而并不将 GPIO 接口单独
|
||||
暴露出来,比如编址、电源管理等。类似编解码器这样的芯片会有复杂的非 GPIO
|
||||
状态。
|
||||
|
||||
任何一个 debugfs 信息导出方法通常应该忽略还未申请作为 GPIO 的信号线。
|
||||
他们可以使用 gpiochip_is_requested()测试,当这个 GPIO 已经申请过了
|
||||
就返回相关的标签,否则返回 NULL。
|
||||
|
||||
|
||||
平台支持
|
||||
-------
|
||||
为了支持这个框架,一个平台的 Kconfig 文件将会 "select"(选择)
|
||||
ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB,并让它的
|
||||
<asm/gpio.h> 包含 <asm-generic/gpio.h>,同时定义三个方法:
|
||||
gpio_get_value()、gpio_set_value()和 gpio_cansleep()。
|
||||
|
||||
它也应提供一个 ARCH_NR_GPIOS 的定义值,这样可以更好地反映该平台 GPIO
|
||||
的实际数量,节省静态表的空间。(这个定义值应该包含片上系统内建 GPIO 和
|
||||
GPIO 扩展器中的数据。)
|
||||
|
||||
ARCH_REQUIRE_GPIOLIB 意味着 gpiolib 核心在这个构架中将总是编译进内核。
|
||||
|
||||
ARCH_WANT_OPTIONAL_GPIOLIB 意味着 gpiolib 核心默认关闭,且用户可以
|
||||
使能它,并将其编译进内核(可选)。
|
||||
|
||||
如果这些选项都没被选择,该平台就不通过 GPIO-lib 支持 GPIO,且代码不可以
|
||||
被用户使能。
|
||||
|
||||
以下这些方法的实现可以直接使用框架代码,并总是通过 gpio_chip 调度:
|
||||
|
||||
#define gpio_get_value __gpio_get_value
|
||||
#define gpio_set_value __gpio_set_value
|
||||
#define gpio_cansleep __gpio_cansleep
|
||||
|
||||
这些定义可以用更理想的实现方法替代,那就是使用经过逻辑优化的内联函数来访问
|
||||
基于特定片上系统的 GPIO。例如,若引用的 GPIO (寄存器位偏移)是常量“12”,
|
||||
读取或设置它可能只需少则两或三个指令,且不会休眠。当这样的优化无法实现时,
|
||||
那些函数必须使用框架提供的代码,那就至少要几十条指令才可以实现。对于用 GPIO
|
||||
模拟的 I/O 接口, 如此精简指令是很有意义的。
|
||||
|
||||
对于片上系统,平台特定代码为片上 GPIO 每个区(bank)定义并注册 gpio_chip
|
||||
实例。那些 GPIO 应该根据芯片厂商的文档进行编码/标签,并直接和电路板原理图
|
||||
对应。他们应该开始于零并终止于平台特定的限制。这些 GPIO(代码)通常从
|
||||
arch_initcall()或者更早的地方集成进平台初始化代码,使这些 GPIO 总是可用,
|
||||
且他们通常可以作为 IRQ 使用。
|
||||
|
||||
板级支持
|
||||
-------
|
||||
对于外部 GPIO 控制器(例如 I2C 或 SPI 扩展器、专用芯片、多功能器件、FPGA
|
||||
或 CPLD),大多数常用板级特定代码都可以注册控制器设备,并保证他们的驱动知道
|
||||
gpiochip_add()所使用的 GPIO 编号。他们的起始编号通常跟在平台特定的 GPIO
|
||||
编号之后。
|
||||
|
||||
例如板级启动代码应该创建结构体指明芯片公开的 GPIO 范围,并使用 platform_data
|
||||
将其传递给每个 GPIO 扩展器芯片。然后芯片驱动中的 probe()例程可以将这个
|
||||
数据传递给 gpiochip_add()。
|
||||
|
||||
初始化顺序很重要。例如,如果一个设备依赖基于 I2C 的(扩展)GPIO,那么它的
|
||||
probe()例程就应该在那个 GPIO 有效以后才可以被调用。这意味着设备应该在
|
||||
GPIO 可以工作之后才可被注册。解决这类依赖的的一种方法是让这种 gpio_chip
|
||||
控制器向板级特定代码提供 setup()和 teardown()回调函数。一旦所有必须的
|
||||
资源可用之后,这些板级特定的回调函数将会注册设备,并可以在这些 GPIO 控制器
|
||||
设备变成无效时移除它们。
|
||||
|
||||
|
||||
用户空间的 Sysfs 接口(可选)
|
||||
========================
|
||||
使用“gpiolib”实现框架的平台可以选择配置一个 GPIO 的 sysfs 用户接口。
|
||||
这不同于 debugfs 接口,因为它提供的是对 GPIO方向和值的控制,而不只显示
|
||||
一个GPIO 的状态摘要。此外,它可以出现在没有调试支持的产品级系统中。
|
||||
|
||||
例如,通过适当的系统硬件文档,用户空间可以知道 GIOP #23 控制 Flash
|
||||
存储器的写保护(用于保护其中 Bootloader 分区)。产品的系统升级可能需要
|
||||
临时解除这个保护:首先导入一个 GPIO,改变其输出状态,然后在重新使能写保护
|
||||
前升级代码。通常情况下,GPIO #23 是不会被触及的,并且内核也不需要知道他。
|
||||
|
||||
根据适当的硬件文档,某些系统的用户空间 GPIO 可以用于确定系统配置数据,
|
||||
这些数据是标准内核不知道的。在某些任务中,简单的用户空间 GPIO 驱动可能是
|
||||
系统真正需要的。
|
||||
|
||||
注意:标准内核驱动中已经存在通用的“LED 和按键”GPIO 任务,分别是:
|
||||
"leds-gpio" 和 "gpio_keys"。请使用这些来替代直接访问 GPIO,因为集成在
|
||||
内核框架中的这类驱动比你在用户空间的代码更好。
|
||||
|
||||
|
||||
Sysfs 中的路径
|
||||
--------------
|
||||
在/sys/class/gpio 中有 3 类入口:
|
||||
|
||||
- 用于在用户空间控制 GPIO 的控制接口;
|
||||
|
||||
- GPIOs 本身;以及
|
||||
|
||||
- GPIO 控制器 ("gpio_chip" 实例)。
|
||||
|
||||
除了这些标准的文件,还包含“device”符号链接。
|
||||
|
||||
控制接口是只写的:
|
||||
|
||||
/sys/class/gpio/
|
||||
|
||||
"export" ... 用户空间可以通过写其编号到这个文件,要求内核导出
|
||||
一个 GPIO 的控制到用户空间。
|
||||
|
||||
例如: 如果内核代码没有申请 GPIO #19,"echo 19 > export"
|
||||
将会为 GPIO #19 创建一个 "gpio19" 节点。
|
||||
|
||||
"unexport" ... 导出到用户空间的逆操作。
|
||||
|
||||
例如: "echo 19 > unexport" 将会移除使用"export"文件导出的
|
||||
"gpio19" 节点。
|
||||
|
||||
GPIO 信号的路径类似 /sys/class/gpio/gpio42/ (对于 GPIO #42 来说),
|
||||
并有如下的读/写属性:
|
||||
|
||||
/sys/class/gpio/gpioN/
|
||||
|
||||
"direction" ... 读取得到 "in" 或 "out"。这个值通常运行写入。
|
||||
写入"out" 时,其引脚的默认输出为低电平。为了确保无故障运行,
|
||||
"low" 或 "high" 的电平值应该写入 GPIO 的配置,作为初始输出值。
|
||||
|
||||
注意:如果内核不支持改变 GPIO 的方向,或者在导出时内核代码没有
|
||||
明确允许用户空间可以重新配置 GPIO 方向,那么这个属性将不存在。
|
||||
|
||||
"value" ... 读取得到 0 (低电平) 或 1 (高电平)。如果 GPIO 配置为
|
||||
输出,这个值允许写操作。任何非零值都以高电平看待。
|
||||
|
||||
如果引脚可以配置为中断信号,且如果已经配置了产生中断的模式
|
||||
(见"edge"的描述),你可以对这个文件使用轮询操作(poll(2)),
|
||||
且轮询操作会在任何中断触发时返回。如果你使用轮询操作(poll(2)),
|
||||
请在 events 中设置 POLLPRI 和 POLLERR。如果你使用轮询操作
|
||||
(select(2)),请在 exceptfds 设置你期望的文件描述符。在
|
||||
轮询操作(poll(2))返回之后,既可以通过 lseek(2)操作读取
|
||||
sysfs 文件的开始部分,也可以关闭这个文件并重新打开它来读取数据。
|
||||
|
||||
"edge" ... 读取得到“none”、“rising”、“falling”或者“both”。
|
||||
将这些字符串写入这个文件可以选择沿触发模式,会使得轮询操作
|
||||
(select(2))在"value"文件中返回。
|
||||
|
||||
这个文件仅有在这个引脚可以配置为可产生中断输入引脚时,才存在。
|
||||
|
||||
"active_low" ... 读取得到 0 (假) 或 1 (真)。写入任何非零值可以
|
||||
翻转这个属性的(读写)值。已存在或之后通过"edge"属性设置了"rising"
|
||||
和 "falling" 沿触发模式的轮询操作(poll(2))将会遵循这个设置。
|
||||
|
||||
GPIO 控制器的路径类似 /sys/class/gpio/gpiochip42/ (对于从#42 GPIO
|
||||
开始实现控制的控制器),并有着以下只读属性:
|
||||
|
||||
/sys/class/gpio/gpiochipN/
|
||||
|
||||
"base" ... 与以上的 N 相同,代表此芯片管理的第一个 GPIO 的编号
|
||||
|
||||
"label" ... 用于诊断 (并不总是只有唯一值)
|
||||
|
||||
"ngpio" ... 此控制器所管理的 GPIO 数量(而 GPIO 编号从 N 到
|
||||
N + ngpio - 1)
|
||||
|
||||
大多数情况下,电路板的文档应当标明每个 GPIO 的使用目的。但是那些编号并不总是
|
||||
固定的,例如在扩展卡上的 GPIO会根据所使用的主板或所在堆叠架构中其他的板子而
|
||||
有所不同。在这种情况下,你可能需要使用 gpiochip 节点(尽可能地结合电路图)来
|
||||
确定给定信号所用的 GPIO 编号。
|
||||
|
||||
|
||||
从内核代码中导出
|
||||
-------------
|
||||
内核代码可以明确地管理那些已通过 gpio_request()申请的 GPIO 的导出:
|
||||
|
||||
/* 导出 GPIO 到用户空间 */
|
||||
int gpio_export(unsigned gpio, bool direction_may_change);
|
||||
|
||||
/* gpio_export()的逆操作 */
|
||||
void gpio_unexport();
|
||||
|
||||
/* 创建一个 sysfs 连接到已导出的 GPIO 节点 */
|
||||
int gpio_export_link(struct device *dev, const char *name,
|
||||
unsigned gpio)
|
||||
|
||||
/* 改变 sysfs 中的一个 GPIO 节点的极性 */
|
||||
int gpio_sysfs_set_active_low(unsigned gpio, int value);
|
||||
|
||||
在一个内核驱动申请一个 GPIO 之后,它可以通过 gpio_export()使其在 sysfs
|
||||
接口中可见。该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间
|
||||
破坏重要的系统状态。
|
||||
|
||||
这个明确的导出有助于(通过使某些实验更容易来)调试,也可以提供一个始终存在的接口,
|
||||
与文档配合作为板级支持包的一部分。
|
||||
|
||||
在 GPIO 被导出之后,gpio_export_link()允许在 sysfs 文件系统的任何地方
|
||||
创建一个到这个 GPIO sysfs 节点的符号链接。这样驱动就可以通过一个描述性的
|
||||
名字,在 sysfs 中他们所拥有的设备下提供一个(到这个 GPIO sysfs 节点的)接口。
|
||||
|
||||
驱动可以使用 gpio_sysfs_set_active_low() 来在用户空间隐藏电路板之间
|
||||
GPIO 线的极性差异。这个仅对 sysfs 接口起作用。极性的改变可以在 gpio_export()
|
||||
前后进行,且之前使能的轮询操作(poll(2))支持(上升或下降沿)将会被重新配置来遵循
|
||||
这个设置。
|
67
Documentation/zh_CN/io_ordering.txt
Normal file
67
Documentation/zh_CN/io_ordering.txt
Normal file
|
@ -0,0 +1,67 @@
|
|||
Chinese translated version of Documentation/io_orderings.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: Lin Yongting <linyongting@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/io_ordering.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 林永听 Lin Yongting <linyongting@gmail.com>
|
||||
中文版翻译者: 林永听 Lin Yongting <linyongting@gmail.com>
|
||||
中文版校译者: 林永听 Lin Yongting <linyongting@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
在某些平台上,所谓的内存映射I/O是弱顺序。在这些平台上,驱动开发者有责任
|
||||
保证I/O内存映射地址的写操作按程序图意的顺序达到设备。通常读取一个“安全”
|
||||
设备寄存器或桥寄存器,触发IO芯片清刷未处理的写操作到达设备后才处理读操作,
|
||||
而达到保证目的。驱动程序通常在spinlock保护的临界区退出之前使用这种技术。
|
||||
这也可以保证后面的写操作只在前面的写操作之后到达设备(这非常类似于内存
|
||||
屏障操作,mb(),不过仅适用于I/O)。
|
||||
|
||||
假设一个设备驱动程的具体例子:
|
||||
|
||||
...
|
||||
CPU A: spin_lock_irqsave(&dev_lock, flags)
|
||||
CPU A: val = readl(my_status);
|
||||
CPU A: ...
|
||||
CPU A: writel(newval, ring_ptr);
|
||||
CPU A: spin_unlock_irqrestore(&dev_lock, flags)
|
||||
...
|
||||
CPU B: spin_lock_irqsave(&dev_lock, flags)
|
||||
CPU B: val = readl(my_status);
|
||||
CPU B: ...
|
||||
CPU B: writel(newval2, ring_ptr);
|
||||
CPU B: spin_unlock_irqrestore(&dev_lock, flags)
|
||||
...
|
||||
|
||||
上述例子中,设备可能会先接收到newval2的值,然后接收到newval的值,问题就
|
||||
发生了。不过很容易通过下面方法来修复:
|
||||
|
||||
...
|
||||
CPU A: spin_lock_irqsave(&dev_lock, flags)
|
||||
CPU A: val = readl(my_status);
|
||||
CPU A: ...
|
||||
CPU A: writel(newval, ring_ptr);
|
||||
CPU A: (void)readl(safe_register); /* 配置寄存器?*/
|
||||
CPU A: spin_unlock_irqrestore(&dev_lock, flags)
|
||||
...
|
||||
CPU B: spin_lock_irqsave(&dev_lock, flags)
|
||||
CPU B: val = readl(my_status);
|
||||
CPU B: ...
|
||||
CPU B: writel(newval2, ring_ptr);
|
||||
CPU B: (void)readl(safe_register); /* 配置寄存器?*/
|
||||
CPU B: spin_unlock_irqrestore(&dev_lock, flags)
|
||||
|
||||
在解决方案中,读取safe_register寄存器,触发IO芯片清刷未处理的写操作,
|
||||
再处理后面的读操作,防止引发数据不一致问题。
|
155
Documentation/zh_CN/magic-number.txt
Normal file
155
Documentation/zh_CN/magic-number.txt
Normal file
|
@ -0,0 +1,155 @@
|
|||
Chinese translated version of Documentation/magic-number.txt
|
||||
|
||||
If you have any comment or update to the content, please post to LKML directly.
|
||||
However, if you have problem communicating in English you can also ask the
|
||||
Chinese maintainer for help. Contact the Chinese maintainer, if this
|
||||
translation is outdated or there is problem with translation.
|
||||
|
||||
Chinese maintainer: Jia Wei Wei <harryxiyou@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/magic-number.txt的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接发信到LKML。如果你使用英文交流有困难的话,也可
|
||||
以向中文版维护者求助。如果本翻译更新不及时或者翻译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 贾威威 Jia Wei Wei <harryxiyou@gmail.com>
|
||||
中文版翻译者: 贾威威 Jia Wei Wei <harryxiyou@gmail.com>
|
||||
中文版校译者: 贾威威 Jia Wei Wei <harryxiyou@gmail.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
这个文件是有关当前使用的魔术值注册表。当你给一个结构添加了一个魔术值,你也应该把这个魔术值添加到这个文件,因为我们最好把用于各种结构的魔术值统一起来。
|
||||
|
||||
使用魔术值来保护内核数据结构是一个非常好的主意。这就允许你在运行期检查(a)一个结构是否已经被攻击,或者(b)你已经给一个例行程序通过了一个错误的结构。后一种情况特别地有用---特别是当你通过一个空指针指向结构体的时候。tty源码,例如,经常通过特定驱动使用这种方法并且反复地排列特定方面的结构。
|
||||
|
||||
使用魔术值的方法是在结构的开始处声明的,如下:
|
||||
|
||||
struct tty_ldisc {
|
||||
int magic;
|
||||
...
|
||||
};
|
||||
|
||||
当你以后给内核添加增强功能的时候,请遵守这条规则!这样就会节省数不清的调试时间,特别是一些古怪的情况,例如,数组超出范围并且重新写了超出部分。遵守这个规则,这些情况可以被快速地,安全地避免。
|
||||
|
||||
Theodore Ts'o
|
||||
31 Mar 94
|
||||
|
||||
给当前的Linux 2.1.55添加魔术表。
|
||||
|
||||
Michael Chastain
|
||||
<mailto:mec@shout.net>
|
||||
22 Sep 1997
|
||||
|
||||
现在应该最新的Linux 2.1.112.因为在特性冻结期间,不能在2.2.x前改变任何东西。这些条目被数域所排序。
|
||||
|
||||
Krzysztof G.Baranowski
|
||||
<mailto: kgb@knm.org.pl>
|
||||
29 Jul 1998
|
||||
|
||||
更新魔术表到Linux 2.5.45。刚好越过特性冻结,但是有可能还会有一些新的魔术值在2.6.x之前融入到内核中。
|
||||
|
||||
Petr Baudis
|
||||
<pasky@ucw.cz>
|
||||
03 Nov 2002
|
||||
|
||||
更新魔术表到Linux 2.5.74。
|
||||
|
||||
Fabian Frederick
|
||||
<ffrederick@users.sourceforge.net>
|
||||
09 Jul 2003
|
||||
|
||||
魔术名 地址 结构 所在文件
|
||||
===========================================================================
|
||||
PG_MAGIC 'P' pg_{read,write}_hdr include/linux/pg.h
|
||||
CMAGIC 0x0111 user include/linux/a.out.h
|
||||
MKISS_DRIVER_MAGIC 0x04bf mkiss_channel drivers/net/mkiss.h
|
||||
HDLC_MAGIC 0x239e n_hdlc drivers/char/n_hdlc.c
|
||||
APM_BIOS_MAGIC 0x4101 apm_user arch/x86/kernel/apm_32.c
|
||||
CYCLADES_MAGIC 0x4359 cyclades_port include/linux/cyclades.h
|
||||
DB_MAGIC 0x4442 fc_info drivers/net/iph5526_novram.c
|
||||
DL_MAGIC 0x444d fc_info drivers/net/iph5526_novram.c
|
||||
FASYNC_MAGIC 0x4601 fasync_struct include/linux/fs.h
|
||||
FF_MAGIC 0x4646 fc_info drivers/net/iph5526_novram.c
|
||||
ISICOM_MAGIC 0x4d54 isi_port include/linux/isicom.h
|
||||
PTY_MAGIC 0x5001 drivers/char/pty.c
|
||||
PPP_MAGIC 0x5002 ppp include/linux/if_pppvar.h
|
||||
SERIAL_MAGIC 0x5301 async_struct include/linux/serial.h
|
||||
SSTATE_MAGIC 0x5302 serial_state include/linux/serial.h
|
||||
SLIP_MAGIC 0x5302 slip drivers/net/slip.h
|
||||
STRIP_MAGIC 0x5303 strip drivers/net/strip.c
|
||||
X25_ASY_MAGIC 0x5303 x25_asy drivers/net/x25_asy.h
|
||||
SIXPACK_MAGIC 0x5304 sixpack drivers/net/hamradio/6pack.h
|
||||
AX25_MAGIC 0x5316 ax_disp drivers/net/mkiss.h
|
||||
TTY_MAGIC 0x5401 tty_struct include/linux/tty.h
|
||||
MGSL_MAGIC 0x5401 mgsl_info drivers/char/synclink.c
|
||||
TTY_DRIVER_MAGIC 0x5402 tty_driver include/linux/tty_driver.h
|
||||
MGSLPC_MAGIC 0x5402 mgslpc_info drivers/char/pcmcia/synclink_cs.c
|
||||
TTY_LDISC_MAGIC 0x5403 tty_ldisc include/linux/tty_ldisc.h
|
||||
USB_SERIAL_MAGIC 0x6702 usb_serial drivers/usb/serial/usb-serial.h
|
||||
FULL_DUPLEX_MAGIC 0x6969 drivers/net/ethernet/dec/tulip/de2104x.c
|
||||
USB_BLUETOOTH_MAGIC 0x6d02 usb_bluetooth drivers/usb/class/bluetty.c
|
||||
RFCOMM_TTY_MAGIC 0x6d02 net/bluetooth/rfcomm/tty.c
|
||||
USB_SERIAL_PORT_MAGIC 0x7301 usb_serial_port drivers/usb/serial/usb-serial.h
|
||||
CG_MAGIC 0x00090255 ufs_cylinder_group include/linux/ufs_fs.h
|
||||
RPORT_MAGIC 0x00525001 r_port drivers/char/rocket_int.h
|
||||
LSEMAGIC 0x05091998 lse drivers/fc4/fc.c
|
||||
GDTIOCTL_MAGIC 0x06030f07 gdth_iowr_str drivers/scsi/gdth_ioctl.h
|
||||
RIEBL_MAGIC 0x09051990 drivers/net/atarilance.c
|
||||
NBD_REQUEST_MAGIC 0x12560953 nbd_request include/linux/nbd.h
|
||||
RED_MAGIC2 0x170fc2a5 (any) mm/slab.c
|
||||
BAYCOM_MAGIC 0x19730510 baycom_state drivers/net/baycom_epp.c
|
||||
ISDN_X25IFACE_MAGIC 0x1e75a2b9 isdn_x25iface_proto_data
|
||||
drivers/isdn/isdn_x25iface.h
|
||||
ECP_MAGIC 0x21504345 cdkecpsig include/linux/cdk.h
|
||||
LSOMAGIC 0x27091997 lso drivers/fc4/fc.c
|
||||
LSMAGIC 0x2a3b4d2a ls drivers/fc4/fc.c
|
||||
WANPIPE_MAGIC 0x414C4453 sdla_{dump,exec} include/linux/wanpipe.h
|
||||
CS_CARD_MAGIC 0x43525553 cs_card sound/oss/cs46xx.c
|
||||
LABELCL_MAGIC 0x4857434c labelcl_info_s include/asm/ia64/sn/labelcl.h
|
||||
ISDN_ASYNC_MAGIC 0x49344C01 modem_info include/linux/isdn.h
|
||||
CTC_ASYNC_MAGIC 0x49344C01 ctc_tty_info drivers/s390/net/ctctty.c
|
||||
ISDN_NET_MAGIC 0x49344C02 isdn_net_local_s drivers/isdn/i4l/isdn_net_lib.h
|
||||
SAVEKMSG_MAGIC2 0x4B4D5347 savekmsg arch/*/amiga/config.c
|
||||
CS_STATE_MAGIC 0x4c4f4749 cs_state sound/oss/cs46xx.c
|
||||
SLAB_C_MAGIC 0x4f17a36d kmem_cache mm/slab.c
|
||||
COW_MAGIC 0x4f4f4f4d cow_header_v1 arch/um/drivers/ubd_user.c
|
||||
I810_CARD_MAGIC 0x5072696E i810_card sound/oss/i810_audio.c
|
||||
TRIDENT_CARD_MAGIC 0x5072696E trident_card sound/oss/trident.c
|
||||
ROUTER_MAGIC 0x524d4157 wan_device [in wanrouter.h pre 3.9]
|
||||
SCC_MAGIC 0x52696368 gs_port drivers/char/scc.h
|
||||
SAVEKMSG_MAGIC1 0x53415645 savekmsg arch/*/amiga/config.c
|
||||
GDA_MAGIC 0x58464552 gda arch/mips/include/asm/sn/gda.h
|
||||
RED_MAGIC1 0x5a2cf071 (any) mm/slab.c
|
||||
EEPROM_MAGIC_VALUE 0x5ab478d2 lanai_dev drivers/atm/lanai.c
|
||||
HDLCDRV_MAGIC 0x5ac6e778 hdlcdrv_state include/linux/hdlcdrv.h
|
||||
PCXX_MAGIC 0x5c6df104 channel drivers/char/pcxx.h
|
||||
KV_MAGIC 0x5f4b565f kernel_vars_s arch/mips/include/asm/sn/klkernvars.h
|
||||
I810_STATE_MAGIC 0x63657373 i810_state sound/oss/i810_audio.c
|
||||
TRIDENT_STATE_MAGIC 0x63657373 trient_state sound/oss/trident.c
|
||||
M3_CARD_MAGIC 0x646e6f50 m3_card sound/oss/maestro3.c
|
||||
FW_HEADER_MAGIC 0x65726F66 fw_header drivers/atm/fore200e.h
|
||||
SLOT_MAGIC 0x67267321 slot drivers/hotplug/cpqphp.h
|
||||
SLOT_MAGIC 0x67267322 slot drivers/hotplug/acpiphp.h
|
||||
LO_MAGIC 0x68797548 nbd_device include/linux/nbd.h
|
||||
OPROFILE_MAGIC 0x6f70726f super_block drivers/oprofile/oprofilefs.h
|
||||
M3_STATE_MAGIC 0x734d724d m3_state sound/oss/maestro3.c
|
||||
VMALLOC_MAGIC 0x87654320 snd_alloc_track sound/core/memory.c
|
||||
KMALLOC_MAGIC 0x87654321 snd_alloc_track sound/core/memory.c
|
||||
PWC_MAGIC 0x89DC10AB pwc_device drivers/usb/media/pwc.h
|
||||
NBD_REPLY_MAGIC 0x96744668 nbd_reply include/linux/nbd.h
|
||||
ENI155_MAGIC 0xa54b872d midway_eprom drivers/atm/eni.h
|
||||
SCI_MAGIC 0xbabeface gs_port drivers/char/sh-sci.h
|
||||
CODA_MAGIC 0xC0DAC0DA coda_file_info include/linux/coda_fs_i.h
|
||||
DPMEM_MAGIC 0xc0ffee11 gdt_pci_sram drivers/scsi/gdth.h
|
||||
YAM_MAGIC 0xF10A7654 yam_port drivers/net/hamradio/yam.c
|
||||
CCB_MAGIC 0xf2691ad2 ccb drivers/scsi/ncr53c8xx.c
|
||||
QUEUE_MAGIC_FREE 0xf7e1c9a3 queue_entry drivers/scsi/arm/queue.c
|
||||
QUEUE_MAGIC_USED 0xf7e1cc33 queue_entry drivers/scsi/arm/queue.c
|
||||
HTB_CMAGIC 0xFEFAFEF1 htb_class net/sched/sch_htb.c
|
||||
NMI_MAGIC 0x48414d4d455201 nmi_s arch/mips/include/asm/sn/nmi.h
|
||||
|
||||
请注意,在声音记忆管理中仍然有一些特殊的为每个驱动定义的魔术值。查看include/sound/sndmagic.h来获取他们完整的列表信息。很多OSS声音驱动拥有自己从声卡PCI ID构建的魔术值-他们也没有被列在这里。
|
||||
|
||||
IrDA子系统也使用了大量的自己的魔术值,查看include/net/irda/irda.h来获取他们完整的信息。
|
||||
|
||||
HFS是另外一个比较大的使用魔术值的文件系统-你可以在fs/hfs/hfs.h中找到他们。
|
212
Documentation/zh_CN/oops-tracing.txt
Normal file
212
Documentation/zh_CN/oops-tracing.txt
Normal file
|
@ -0,0 +1,212 @@
|
|||
Chinese translated version of Documentation/oops-tracing.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: Dave Young <hidave.darkstar@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/oops-tracing.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 杨瑞 Dave Young <hidave.darkstar@gmail.com>
|
||||
中文版翻译者: 杨瑞 Dave Young <hidave.darkstar@gmail.com>
|
||||
中文版校译者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
王聪 Wang Cong <xiyou.wangcong@gmail.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
注意: ksymoops 在2.6中是没有用的。 请以原有格式使用Oops(来自dmesg,等等)。
|
||||
忽略任何这样那样关于“解码Oops”或者“通过ksymoops运行”的文档。 如果你贴出运行过
|
||||
ksymoops的来自2.6的Oops,人们只会让你重贴一次。
|
||||
|
||||
快速总结
|
||||
-------------
|
||||
|
||||
发现Oops并发送给看似相关的内核领域的维护者。别太担心对不上号。如果你不确定就发给
|
||||
和你所做的事情相关的代码的负责人。 如果可重现试着描述怎样重构。 那甚至比oops更有
|
||||
价值。
|
||||
|
||||
如果你对于发送给谁一无所知, 发给linux-kernel@vger.kernel.org。感谢你帮助Linux
|
||||
尽可能地稳定。
|
||||
|
||||
Oops在哪里?
|
||||
----------------------
|
||||
|
||||
通常Oops文本由klogd从内核缓冲区里读取并传给syslogd,由syslogd写到syslog文件中,
|
||||
典型地是/var/log/messages(依赖于/etc/syslog.conf)。有时klogd崩溃了,这种情况下你
|
||||
能够运行dmesg > file来从内核缓冲区中读取数据并保存下来。 否则你可以
|
||||
cat /proc/kmsg > file, 然而你必须介入中止传输, kmsg是一个“永不结束的文件”。如
|
||||
果机器崩溃坏到你不能输入命令或者磁盘不可用那么你有三种选择:-
|
||||
|
||||
(1) 手抄屏幕上的文本待机器重启后再输入计算机。 麻烦但如果没有针对崩溃的准备,
|
||||
这是仅有的选择。 另外,你可以用数码相机把屏幕拍下来-不太好,但比没有强。 如果信
|
||||
息滚动到了终端的上面,你会发现以高分辩率启动(比如,vga=791)会让你读到更多的文
|
||||
本。(注意:这需要vesafb,所以对‘早期’的oops没有帮助)
|
||||
|
||||
(2)用串口终端启动(请参看Documentation/serial-console.txt),运行一个null
|
||||
modem到另一台机器并用你喜欢的通讯工具获取输出。Minicom工作地很好。
|
||||
|
||||
(3)使用Kdump(请参看Documentation/kdump/kdump.txt),
|
||||
使用在Documentation/kdump/gdbmacros.txt中定义的dmesg gdb宏,从旧的内存中提取内核
|
||||
环形缓冲区。
|
||||
|
||||
完整信息
|
||||
----------------
|
||||
|
||||
注意:以下来自于Linus的邮件适用于2.4内核。 我因为历史原因保留了它,并且因为其中
|
||||
一些信息仍然适用。 特别注意的是,请忽略任何ksymoops的引用。
|
||||
|
||||
From: Linus Torvalds <torvalds@osdl.org>
|
||||
|
||||
怎样跟踪Oops.. [原发到linux-kernel的一封邮件]
|
||||
|
||||
主要的窍门是有五年和这些烦人的oops消息打交道的经验;-)
|
||||
|
||||
实际上,你有办法使它更简单。我有两个不同的方法:
|
||||
|
||||
gdb /usr/src/linux/vmlinux
|
||||
gdb> disassemble <offending_function>
|
||||
|
||||
那是发现问题的简单办法,至少如果bug报告做的好的情况下(象这个一样-运行ksymoops
|
||||
得到oops发生的函数及函数内的偏移)。
|
||||
|
||||
哦,如果报告发生的内核以相同的编译器和相似的配置编译它会有帮助的。
|
||||
|
||||
另一件要做的事是反汇编bug报告的“Code”部分:ksymoops也会用正确的工具来做这件事,
|
||||
但如果没有那些工具你可以写一个傻程序:
|
||||
|
||||
char str[] = "\xXX\xXX\xXX...";
|
||||
main(){}
|
||||
|
||||
并用gcc -g编译它然后执行“disassemble str”(XX部分是由Oops报告的值-你可以仅剪切
|
||||
粘贴并用“\x”替换空格-我就是这么做的,因为我懒得写程序自动做这一切)。
|
||||
|
||||
另外,你可以用scripts/decodecode这个shell脚本。它的使用方法是:
|
||||
decodecode < oops.txt
|
||||
|
||||
“Code”之后的十六进制字节可能(在某些架构上)有一些当前指令之前的指令字节以及
|
||||
当前和之后的指令字节
|
||||
|
||||
Code: f9 0f 8d f9 00 00 00 8d 42 0c e8 dd 26 11 c7 a1 60 ea 2b f9 8b 50 08 a1
|
||||
64 ea 2b f9 8d 34 82 8b 1e 85 db 74 6d 8b 15 60 ea 2b f9 <8b> 43 04 39 42 54
|
||||
7e 04 40 89 42 54 8b 43 04 3b 05 00 f6 52 c0
|
||||
|
||||
最后,如果你想知道代码来自哪里,你可以:
|
||||
|
||||
cd /usr/src/linux
|
||||
make fs/buffer.s # 或任何产生BUG的文件
|
||||
|
||||
然后你会比gdb反汇编更清楚的知道发生了什么。
|
||||
|
||||
现在,问题是把你所拥有的所有数据结合起来:C源码(关于它应该怎样的一般知识),
|
||||
汇编代码及其反汇编得到的代码(另外还有从“oops”消息得到的寄存器状态-对了解毁坏的
|
||||
指针有用,而且当你有了汇编代码你也能拿其它的寄存器和任何它们对应的C表达式做匹配
|
||||
)。
|
||||
|
||||
实际上,你仅需看看哪里不匹配(这个例子是“Code”反汇编和编译器生成的代码不匹配)。
|
||||
然后你须要找出为什么不匹配。通常很简单-你看到代码使用了空指针然后你看代码想知道
|
||||
空指针是怎么出现的,还有检查它是否合法..
|
||||
|
||||
现在,如果明白这是一项耗时的工作而且需要一丁点儿的专心,没错。这就是我为什么大多
|
||||
只是忽略那些没有符号表信息的崩溃报告的原因:简单的说太难查找了(我有一些
|
||||
程序用于在内核代码段中搜索特定的模式,而且有时我也已经能找出那些崩溃的地方,但是
|
||||
仅仅是找出正确的序列也确实需要相当扎实的内核知识)
|
||||
|
||||
_有时_会发生这种情况,我仅看到崩溃中的反汇编代码序列, 然后我马上就明白问题出在
|
||||
哪里。这时我才意识到自己干这个工作已经太长时间了;-)
|
||||
|
||||
Linus
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
关于Oops跟踪的注解:
|
||||
|
||||
为了帮助Linus和其它内核开发者,klogd纳入了大量的支持来处理保护错误。为了拥有对
|
||||
地址解析的完整支持至少应该使用1.3-pl3的sysklogd包。
|
||||
|
||||
当保护错误发生时,klogd守护进程自动把内核日志信息中的重要地址翻译成它们相应的符
|
||||
号。
|
||||
|
||||
klogd执行两种类型的地址解析。首先是静态翻译其次是动态翻译。静态翻译和ksymoops
|
||||
一样使用System.map文件。为了做静态翻译klogd守护进程必须在初始化时能找到system
|
||||
map文件。关于klogd怎样搜索map文件请参看klogd手册页。
|
||||
|
||||
动态地址翻译在使用内核可装载模块时很重要。 因为内核模块的内存是从内核动态内存池
|
||||
里分配的,所以不管是模块开始位置还是模块中函数和符号的位置都不是固定的。
|
||||
|
||||
内核支持允许程序决定装载哪些模块和它们在内存中位置的系统调用。使用这些系统调用
|
||||
klogd守护进程生成一张符号表用于调试发生在可装载模块中的保护错误。
|
||||
|
||||
至少klogd会提供产生保护错误的模块名。还可有额外的符号信息供可装载模块开发者选择
|
||||
以从模块中输出符号信息。
|
||||
|
||||
因为内核模块环境可能是动态的,所以必须有一种机制当模块环境发生改变时来通知klogd
|
||||
守护进程。 有一些可用的命令行选项允许klogd向当前执行中的守护进程发送信号,告知符
|
||||
号信息应该被刷新了。 更多信息请参看klogd手册页。
|
||||
|
||||
sysklogd发布时包含一个补丁修改了modules-2.0.0包,无论何时一个模块装载或者卸载都
|
||||
会自动向klogd发送信号。打上这个补丁提供了必要的对调试发生于内核可装载模块的保护
|
||||
错误的无缝支持。
|
||||
|
||||
以下是被klogd处理过的发生在可装载模块中的一个保护错误例子:
|
||||
---------------------------------------------------------------------------
|
||||
Aug 29 09:51:01 blizard kernel: Unable to handle kernel paging request at virtual address f15e97cc
|
||||
Aug 29 09:51:01 blizard kernel: current->tss.cr3 = 0062d000, %cr3 = 0062d000
|
||||
Aug 29 09:51:01 blizard kernel: *pde = 00000000
|
||||
Aug 29 09:51:01 blizard kernel: Oops: 0002
|
||||
Aug 29 09:51:01 blizard kernel: CPU: 0
|
||||
Aug 29 09:51:01 blizard kernel: EIP: 0010:[oops:_oops+16/3868]
|
||||
Aug 29 09:51:01 blizard kernel: EFLAGS: 00010212
|
||||
Aug 29 09:51:01 blizard kernel: eax: 315e97cc ebx: 003a6f80 ecx: 001be77b edx: 00237c0c
|
||||
Aug 29 09:51:01 blizard kernel: esi: 00000000 edi: bffffdb3 ebp: 00589f90 esp: 00589f8c
|
||||
Aug 29 09:51:01 blizard kernel: ds: 0018 es: 0018 fs: 002b gs: 002b ss: 0018
|
||||
Aug 29 09:51:01 blizard kernel: Process oops_test (pid: 3374, process nr: 21, stackpage=00589000)
|
||||
Aug 29 09:51:01 blizard kernel: Stack: 315e97cc 00589f98 0100b0b4 bffffed4 0012e38e 00240c64 003a6f80 00000001
|
||||
Aug 29 09:51:01 blizard kernel: 00000000 00237810 bfffff00 0010a7fa 00000003 00000001 00000000 bfffff00
|
||||
Aug 29 09:51:01 blizard kernel: bffffdb3 bffffed4 ffffffda 0000002b 0007002b 0000002b 0000002b 00000036
|
||||
Aug 29 09:51:01 blizard kernel: Call Trace: [oops:_oops_ioctl+48/80] [_sys_ioctl+254/272] [_system_call+82/128]
|
||||
Aug 29 09:51:01 blizard kernel: Code: c7 00 05 00 00 00 eb 08 90 90 90 90 90 90 90 90 89 ec 5d c3
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Dr. G.W. Wettstein Oncology Research Div. Computing Facility
|
||||
Roger Maris Cancer Center INTERNET: greg@wind.rmcc.com
|
||||
820 4th St. N.
|
||||
Fargo, ND 58122
|
||||
Phone: 701-234-7556
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
受污染的内核
|
||||
|
||||
一些oops报告在程序记数器之后包含字符串'Tainted: '。这表明内核已经被一些东西给污
|
||||
染了。 该字符串之后紧跟着一系列的位置敏感的字符,每个代表一个特定的污染值。
|
||||
|
||||
1:'G'如果所有装载的模块都有GPL或相容的许可证,'P'如果装载了任何的专有模块。
|
||||
没有模块MODULE_LICENSE或者带有insmod认为是与GPL不相容的的MODULE_LICENSE的模块被
|
||||
认定是专有的。
|
||||
|
||||
2:'F'如果有任何通过“insmod -f”被强制装载的模块,' '如果所有模块都被正常装载。
|
||||
|
||||
3:'S'如果oops发生在SMP内核中,运行于没有证明安全运行多处理器的硬件。 当前这种
|
||||
情况仅限于几种不支持SMP的速龙处理器。
|
||||
|
||||
4:'R'如果模块通过“insmod -f”被强制装载,' '如果所有模块都被正常装载。
|
||||
|
||||
5:'M'如果任何处理器报告了机器检查异常,' '如果没有发生机器检查异常。
|
||||
|
||||
6:'B'如果页释放函数发现了一个错误的页引用或者一些非预期的页标志。
|
||||
|
||||
7:'U'如果用户或者用户应用程序特别请求设置污染标志,否则' '。
|
||||
|
||||
8:'D'如果内核刚刚死掉,比如有OOPS或者BUG。
|
||||
|
||||
使用'Tainted: '字符串的主要原因是要告诉内核调试者,这是否是一个干净的内核亦或发
|
||||
生了任何的不正常的事。污染是永久的:即使出错的模块已经被卸载了,污染值仍然存在,
|
||||
以表明内核不再值得信任。
|
100
Documentation/zh_CN/sparse.txt
Normal file
100
Documentation/zh_CN/sparse.txt
Normal file
|
@ -0,0 +1,100 @@
|
|||
Chinese translated version of Documentation/sparse.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: Li Yang <leo@zh-kernel.org>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/sparse.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
中文版维护者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
中文版翻译者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Copyright 2004 Linus Torvalds
|
||||
Copyright 2004 Pavel Machek <pavel@ucw.cz>
|
||||
Copyright 2006 Bob Copeland <me@bobcopeland.com>
|
||||
|
||||
使用 sparse 工具做类型检查
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
"__bitwise" 是一种类型属性,所以你应该这样使用它:
|
||||
|
||||
typedef int __bitwise pm_request_t;
|
||||
|
||||
enum pm_request {
|
||||
PM_SUSPEND = (__force pm_request_t) 1,
|
||||
PM_RESUME = (__force pm_request_t) 2
|
||||
};
|
||||
|
||||
这样会使 PM_SUSPEND 和 PM_RESUME 成为位方式(bitwise)整数(使用"__force"
|
||||
是因为 sparse 会抱怨改变位方式的类型转换,但是这里我们确实需要强制进行转
|
||||
换)。而且因为所有枚举值都使用了相同的类型,这里的"enum pm_request"也将
|
||||
会使用那个类型做为底层实现。
|
||||
|
||||
而且使用 gcc 编译的时候,所有的 __bitwise/__force 都会消失,最后在 gcc
|
||||
看来它们只不过是普通的整数。
|
||||
|
||||
坦白来说,你并不需要使用枚举类型。上面那些实际都可以浓缩成一个特殊的"int
|
||||
__bitwise"类型。
|
||||
|
||||
所以更简单的办法只要这样做:
|
||||
|
||||
typedef int __bitwise pm_request_t;
|
||||
|
||||
#define PM_SUSPEND ((__force pm_request_t) 1)
|
||||
#define PM_RESUME ((__force pm_request_t) 2)
|
||||
|
||||
现在你就有了严格的类型检查所需要的所有基础架构。
|
||||
|
||||
一个小提醒:常数整数"0"是特殊的。你可以直接把常数零当作位方式整数使用而
|
||||
不用担心 sparse 会抱怨。这是因为"bitwise"(恰如其名)是用来确保不同位方
|
||||
式类型不会被弄混(小尾模式,大尾模式,cpu尾模式,或者其他),对他们来说
|
||||
常数"0"确实是特殊的。
|
||||
|
||||
获取 sparse 工具
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
你可以从 Sparse 的主页获取最新的发布版本:
|
||||
|
||||
http://www.kernel.org/pub/linux/kernel/people/josh/sparse/
|
||||
|
||||
或者,你也可以使用 git 克隆最新的 sparse 开发版本:
|
||||
|
||||
git://git.kernel.org/pub/scm/linux/kernel/git/josh/sparse.git
|
||||
|
||||
DaveJ 把每小时自动生成的 git 源码树 tar 包放在以下地址:
|
||||
|
||||
http://www.codemonkey.org.uk/projects/git-snapshots/sparse/
|
||||
|
||||
一旦你下载了源码,只要以普通用户身份运行:
|
||||
|
||||
make
|
||||
make install
|
||||
|
||||
它将会被自动安装到你的 ~/bin 目录下。
|
||||
|
||||
使用 sparse 工具
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
用"make C=1"命令来编译内核,会对所有重新编译的 C 文件使用 sparse 工具。
|
||||
或者使用"make C=2"命令,无论文件是否被重新编译都会对其使用 sparse 工具。
|
||||
如果你已经编译了内核,用后一种方式可以很快地检查整个源码树。
|
||||
|
||||
make 的可选变量 CHECKFLAGS 可以用来向 sparse 工具传递参数。编译系统会自
|
||||
动向 sparse 工具传递 -Wbitwise 参数。你可以定义 __CHECK_ENDIAN__ 来进行
|
||||
大小尾检查。
|
||||
|
||||
make C=2 CHECKFLAGS="-D__CHECK_ENDIAN__"
|
||||
|
||||
这些检查默认都是被关闭的,因为他们通常会产生大量的警告。
|
157
Documentation/zh_CN/stable_api_nonsense.txt
Normal file
157
Documentation/zh_CN/stable_api_nonsense.txt
Normal file
|
@ -0,0 +1,157 @@
|
|||
Chinese translated version of Documentation/stable_api_nonsense.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have problem
|
||||
communicating in English you can also ask the Chinese maintainer for help.
|
||||
Contact the Chinese maintainer, if this translation is outdated or there
|
||||
is problem with translation.
|
||||
|
||||
Maintainer: Greg Kroah-Hartman <greg@kroah.com>
|
||||
Chinese maintainer: TripleX Chung <zhongyu@18mail.cn>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/stable_api_nonsense.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Greg Kroah-Hartman <greg@kroah.com>
|
||||
中文版维护者: 钟宇 TripleX Chung <zhongyu@18mail.cn>
|
||||
中文版翻译者: 钟宇 TripleX Chung <zhongyu@18mail.cn>
|
||||
中文版校译者: 李阳 Li Yang <leoli@freescale.com>
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
写作本文档的目的,是为了解释为什么Linux既没有二进制内核接口,也没有稳定
|
||||
的内核接口。这里所说的内核接口,是指内核里的接口,而不是内核和用户空间
|
||||
的接口。内核到用户空间的接口,是提供给应用程序使用的系统调用,系统调用
|
||||
在历史上几乎没有过变化,将来也不会有变化。我有一些老应用程序是在0.9版本
|
||||
或者更早版本的内核上编译的,在使用2.6版本内核的Linux发布上依然用得很好
|
||||
。用户和应用程序作者可以将这个接口看成是稳定的。
|
||||
|
||||
|
||||
执行纲要
|
||||
--------
|
||||
|
||||
你也许以为自己想要稳定的内核接口,但是你不清楚你要的实际上不是它。你需
|
||||
要的其实是稳定的驱动程序,而你只有将驱动程序放到公版内核的源代码树里,
|
||||
才有可能达到这个目的。而且这样做还有很多其它好处,正是因为这些好处使得
|
||||
Linux能成为强壮,稳定,成熟的操作系统,这也是你最开始选择Linux的原因。
|
||||
|
||||
|
||||
入门
|
||||
-----
|
||||
|
||||
只有那些写驱动程序的“怪人”才会担心内核接口的改变,对广大用户来说,既
|
||||
看不到内核接口,也不需要去关心它。
|
||||
|
||||
首先,我不打算讨论关于任何非GPL许可的内核驱动的法律问题,这些非GPL许可
|
||||
的驱动程序包括不公开源代码,隐藏源代码,二进制或者是用源代码包装,或者
|
||||
是其它任何形式的不能以GPL许可公开源代码的驱动程序。如果有法律问题,请咨
|
||||
询律师,我只是一个程序员,所以我只打算探讨技术问题(不是小看法律问题,
|
||||
法律问题很实际,并且需要一直关注)。
|
||||
|
||||
既然只谈技术问题,我们就有了下面两个主题:二进制内核接口和稳定的内核源
|
||||
代码接口。这两个问题是互相关联的,让我们先解决掉二进制接口的问题。
|
||||
|
||||
|
||||
二进制内核接口
|
||||
--------------
|
||||
假如我们有一个稳定的内核源代码接口,那么自然而然的,我们就拥有了稳定的
|
||||
二进制接口,是这样的吗?错。让我们看看关于Linux内核的几点事实:
|
||||
- 取决于所用的C编译器的版本,不同的内核数据结构里的结构体的对齐方
|
||||
式会有差别,代码中不同函数的表现形式也不一样(函数是不是被inline编译取
|
||||
决于编译器行为)。不同的函数的表现形式并不重要,但是数据结构内部的对齐
|
||||
方式很关键。
|
||||
- 取决于内核的配置选项,不同的选项会让内核的很多东西发生改变:
|
||||
- 同一个结构体可能包含不同的成员变量
|
||||
- 有的函数可能根本不会被实现(比如编译的时候没有选择SMP支持
|
||||
,一些锁函数就会被定义成空函数)。
|
||||
- 内核使用的内存会以不同的方式对齐,这取决于不同的内核配置选
|
||||
项。
|
||||
- Linux可以在很多的不同体系结构的处理器上运行。在某个体系结构上编
|
||||
译好的二进制驱动程序,不可能在另外一个体系结构上正确的运行。
|
||||
|
||||
对于一个特定的内核,满足这些条件并不难,使用同一个C编译器和同样的内核配
|
||||
置选项来编译驱动程序模块就可以了。这对于给一个特定Linux发布的特定版本提
|
||||
供驱动程序,是完全可以满足需求的。但是如果你要给不同发布的不同版本都发
|
||||
布一个驱动程序,就需要在每个发布上用不同的内核设置参数都编译一次内核,
|
||||
这简直跟噩梦一样。而且还要注意到,每个Linux发布还提供不同的Linux内核,
|
||||
这些内核都针对不同的硬件类型进行了优化(有很多种不同的处理器,还有不同
|
||||
的内核设置选项)。所以每发布一次驱动程序,都需要提供很多不同版本的内核
|
||||
模块。
|
||||
|
||||
相信我,如果你真的要采取这种发布方式,一定会慢慢疯掉,我很久以前就有过
|
||||
深刻的教训...
|
||||
|
||||
|
||||
稳定的内核源代码接口
|
||||
--------------------
|
||||
|
||||
如果有人不将他的内核驱动程序,放入公版内核的源代码树,而又想让驱动程序
|
||||
一直保持在最新的内核中可用,那么这个话题将会变得没完没了。
|
||||
内核开发是持续而且快节奏的,从来都不会慢下来。内核开发人员在当前接口中
|
||||
找到bug,或者找到更好的实现方式。一旦发现这些,他们就很快会去修改当前的
|
||||
接口。修改接口意味着,函数名可能会改变,结构体可能被扩充或者删减,函数
|
||||
的参数也可能发生改变。一旦接口被修改,内核中使用这些接口的地方需要同时
|
||||
修正,这样才能保证所有的东西继续工作。
|
||||
|
||||
举一个例子,内核的USB驱动程序接口在USB子系统的整个生命周期中,至少经历
|
||||
了三次重写。这些重写解决以下问题:
|
||||
- 把数据流从同步模式改成非同步模式,这个改动减少了一些驱动程序的
|
||||
复杂度,提高了所有USB驱动程序的吞吐率,这样几乎所有的USB设备都能以最大
|
||||
速率工作了。
|
||||
- 修改了USB核心代码中为USB驱动分配数据包内存的方式,所有的驱动都
|
||||
需要提供更多的参数给USB核心,以修正了很多已经被记录在案的死锁。
|
||||
|
||||
这和一些封闭源代码的操作系统形成鲜明的对比,在那些操作系统上,不得不额
|
||||
外的维护旧的USB接口。这导致了一个可能性,新的开发者依然会不小心使用旧的
|
||||
接口,以不恰当的方式编写代码,进而影响到操作系统的稳定性。
|
||||
在上面的例子中,所有的开发者都同意这些重要的改动,在这样的情况下修改代
|
||||
价很低。如果Linux保持一个稳定的内核源代码接口,那么就得创建一个新的接口
|
||||
;旧的,有问题的接口必须一直维护,给Linux USB开发者带来额外的工作。既然
|
||||
所有的Linux USB驱动的作者都是利用自己的时间工作,那么要求他们去做毫无意
|
||||
义的免费额外工作,是不可能的。
|
||||
安全问题对Linux来说十分重要。一个安全问题被发现,就会在短时间内得到修
|
||||
正。在很多情况下,这将导致Linux内核中的一些接口被重写,以从根本上避免安
|
||||
全问题。一旦接口被重写,所有使用这些接口的驱动程序,必须同时得到修正,
|
||||
以确定安全问题已经得到修复并且不可能在未来还有同样的安全问题。如果内核
|
||||
内部接口不允许改变,那么就不可能修复这样的安全问题,也不可能确认这样的
|
||||
安全问题以后不会发生。
|
||||
开发者一直在清理内核接口。如果一个接口没有人在使用了,它就会被删除。这
|
||||
样可以确保内核尽可能的小,而且所有潜在的接口都会得到尽可能完整的测试
|
||||
(没有人使用的接口是不可能得到良好的测试的)。
|
||||
|
||||
|
||||
要做什么
|
||||
-------
|
||||
|
||||
如果你写了一个Linux内核驱动,但是它还不在Linux源代码树里,作为一个开发
|
||||
者,你应该怎么做?为每个发布的每个版本提供一个二进制驱动,那简直是一个
|
||||
噩梦,要跟上永远处于变化之中的内核接口,也是一件辛苦活。
|
||||
很简单,让你的驱动进入内核源代码树(要记得我们在谈论的是以GPL许可发行
|
||||
的驱动,如果你的代码不符合GPL,那么祝你好运,你只能自己解决这个问题了,
|
||||
你这个吸血鬼<把Andrew和Linus对吸血鬼的定义链接到这里>)。当你的代码加入
|
||||
公版内核源代码树之后,如果一个内核接口改变,你的驱动会直接被修改接口的
|
||||
那个人修改。保证你的驱动永远都可以编译通过,并且一直工作,你几乎不需要
|
||||
做什么事情。
|
||||
|
||||
把驱动放到内核源代码树里会有很多的好处:
|
||||
- 驱动的质量会提升,而维护成本(对原始作者来说)会下降。
|
||||
- 其他人会给驱动添加新特性。
|
||||
- 其他人会找到驱动中的bug并修复。
|
||||
- 其他人会在驱动中找到性能优化的机会。
|
||||
- 当外部的接口的改变需要修改驱动程序的时候,其他人会修改驱动程序
|
||||
。
|
||||
- 不需要联系任何发行商,这个驱动会自动的随着所有的Linux发布一起发
|
||||
布。
|
||||
|
||||
和别的操作系统相比,Linux为更多不同的设备提供现成的驱动,而且能在更多不
|
||||
同体系结构的处理器上支持这些设备。这个经过考验的开发模式,必然是错不了
|
||||
的 :)
|
||||
|
||||
-------------
|
||||
感谢 Randy Dunlap, Andrew Morton, David Brownell, Hanna Linder,
|
||||
Robert Love, and Nishanth Aravamudan 对于本文档早期版本的评审和建议。
|
||||
|
||||
英文版维护者: Greg Kroah-Hartman <greg@kroah.com>
|
66
Documentation/zh_CN/stable_kernel_rules.txt
Normal file
66
Documentation/zh_CN/stable_kernel_rules.txt
Normal file
|
@ -0,0 +1,66 @@
|
|||
Chinese translated version of Documentation/stable_kernel_rules.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Chinese maintainer: TripleX Chung <triplex@zh-kernel.org>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/stable_kernel_rules.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
|
||||
中文版维护者: 钟宇 TripleX Chung <triplex@zh-kernel.org>
|
||||
中文版翻译者: 钟宇 TripleX Chung <triplex@zh-kernel.org>
|
||||
中文版校译者: 李阳 Li Yang <leo@zh-kernel.org>
|
||||
Kangkai Yin <e12051@motorola.com>
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
关于Linux 2.6稳定版发布,所有你想知道的事情。
|
||||
|
||||
关于哪些类型的补丁可以被接收进入稳定版代码树,哪些不可以的规则:
|
||||
|
||||
- 必须是显而易见的正确,并且经过测试的。
|
||||
- 连同上下文,不能大于100行。
|
||||
- 必须只修正一件事情。
|
||||
- 必须修正了一个给大家带来麻烦的真正的bug(不是“这也许是一个问题...”
|
||||
那样的东西)。
|
||||
- 必须修正带来如下后果的问题:编译错误(对被标记为CONFIG_BROKEN的例外),
|
||||
内核崩溃,挂起,数据损坏,真正的安全问题,或者一些类似“哦,这不
|
||||
好”的问题。简短的说,就是一些致命的问题。
|
||||
- 没有“理论上的竞争条件”,除非能给出竞争条件如何被利用的解释。
|
||||
- 不能存在任何的“琐碎的”修正(拼写修正,去掉多余空格之类的)。
|
||||
- 必须被相关子系统的维护者接受。
|
||||
- 必须遵循Documentation/SubmittingPatches里的规则。
|
||||
|
||||
向稳定版代码树提交补丁的过程:
|
||||
|
||||
- 在确认了补丁符合以上的规则后,将补丁发送到stable@vger.kernel.org。
|
||||
- 如果补丁被接受到队列里,发送者会收到一个ACK回复,如果没有被接受,收
|
||||
到的是NAK回复。回复需要几天的时间,这取决于开发者的时间安排。
|
||||
- 被接受的补丁会被加到稳定版本队列里,等待其他开发者的审查。
|
||||
- 安全方面的补丁不要发到这个列表,应该发送到security@kernel.org。
|
||||
|
||||
审查周期:
|
||||
|
||||
- 当稳定版的维护者决定开始一个审查周期,补丁将被发送到审查委员会,以
|
||||
及被补丁影响的领域的维护者(除非提交者就是该领域的维护者)并且抄送
|
||||
到linux-kernel邮件列表。
|
||||
- 审查委员会有48小时的时间,用来决定给该补丁回复ACK还是NAK。
|
||||
- 如果委员会中有成员拒绝这个补丁,或者linux-kernel列表上有人反对这个
|
||||
补丁,并提出维护者和审查委员会之前没有意识到的问题,补丁会从队列中
|
||||
丢弃。
|
||||
- 在审查周期结束的时候,那些得到ACK回应的补丁将会被加入到最新的稳定版
|
||||
发布中,一个新的稳定版发布就此产生。
|
||||
- 安全性补丁将从内核安全小组那里直接接收到稳定版代码树中,而不是通过
|
||||
通常的审查周期。请联系内核安全小组以获得关于这个过程的更多细节。
|
||||
|
||||
审查委员会:
|
||||
- 由一些自愿承担这项任务的内核开发者,和几个非志愿的组成。
|
277
Documentation/zh_CN/video4linux/omap3isp.txt
Normal file
277
Documentation/zh_CN/video4linux/omap3isp.txt
Normal file
|
@ -0,0 +1,277 @@
|
|||
Chinese translated version of Documentation/video4linux/omap3isp.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
Sakari Ailus <sakari.ailus@iki.fi>
|
||||
David Cohen <dacohen@gmail.com>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/video4linux/omap3isp.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
英文版维护者: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
Sakari Ailus <sakari.ailus@iki.fi>
|
||||
David Cohen <dacohen@gmail.com>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
OMAP 3 图像信号处理器 (ISP) 驱动
|
||||
|
||||
Copyright (C) 2010 Nokia Corporation
|
||||
Copyright (C) 2009 Texas Instruments, Inc.
|
||||
|
||||
联系人: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
Sakari Ailus <sakari.ailus@iki.fi>
|
||||
David Cohen <dacohen@gmail.com>
|
||||
|
||||
|
||||
介绍
|
||||
===
|
||||
|
||||
本文档介绍了由 drivers/media/video/omap3isp 加载的德州仪器
|
||||
(TI)OMAP 3 图像信号处理器 (ISP) 驱动。原始驱动由德州仪器(TI)
|
||||
编写,但此后由诺基亚重写了两次。
|
||||
|
||||
驱动已在以下 OMAP 3 系列的芯片中成功使用:
|
||||
|
||||
3430
|
||||
3530
|
||||
3630
|
||||
|
||||
驱动实现了 V4L2、媒体控制器和 v4l2_subdev 接口。支持内核中使用
|
||||
v4l2_subdev 接口的传感器、镜头和闪光灯驱动。
|
||||
|
||||
|
||||
拆分为子设备
|
||||
==========
|
||||
|
||||
OMAP 3 ISP 被拆分为 V4L2 子设备,ISP中的每个模块都由一个子设备
|
||||
来表示。每个子设备向用户空间提供一个 V4L2 子设备接口。
|
||||
|
||||
OMAP3 ISP CCP2
|
||||
OMAP3 ISP CSI2a
|
||||
OMAP3 ISP CCDC
|
||||
OMAP3 ISP preview
|
||||
OMAP3 ISP resizer
|
||||
OMAP3 ISP AEWB
|
||||
OMAP3 ISP AF
|
||||
OMAP3 ISP histogram
|
||||
|
||||
ISP 中每个可能的连接都通过一个链接嵌入到媒体控制器接口中。详见例程 [2]。
|
||||
|
||||
|
||||
控制 OMAP 3 ISP
|
||||
==============
|
||||
|
||||
通常,对 OMAP 3 ISP 的配置会在下一帧起始时生效。在传感器垂直消隐期间,
|
||||
模块变为空闲时完成配置。在内存到内存的操作中,视频管道一次处理一帧。
|
||||
应用配置应在帧间完成。
|
||||
|
||||
ISP 中的所有模块,除 CSI-2 和 (可能存在的)CCP2 接收器外,都必须
|
||||
接收完整的帧数据。因此,传感器必须保证从不发送部分帧数据给ISP。
|
||||
|
||||
Autoidle(自动空闲)功能至少在 3430 的 ISP 模块中确实存在一些问题。
|
||||
当 omap3isp 模块参数 autoidle 非零时,autoidle(自动空闲)功能
|
||||
仅在 3630 中启用了。
|
||||
|
||||
|
||||
事件机制
|
||||
======
|
||||
|
||||
OMAP 3 ISP 驱动在 CCDC 和统计(AEWB、AF 和 直方图)子设备中支持
|
||||
V4L2 事件机制接口。
|
||||
|
||||
CCDC 子设备通过 HS_VS 中断,处理 V4L2_EVENT_FRAME_SYNC 类型
|
||||
事件,用于告知帧起始。早期版本的驱动则使用 V4L2_EVENT_OMAP3ISP_HS_VS。
|
||||
当在 CCDC 模块中接收到起始帧的第一行时,会准确地触发事件。这个事件
|
||||
可以在 CCDC 子设备中“订阅”。
|
||||
|
||||
(当使用并行接口时,必须注意正确地配置 VS 信号极性。而当使用串行接收时
|
||||
这个会自动校正。)
|
||||
|
||||
每个统计子设备都可以产生事件。每当一个统计缓冲区可由用户空间应用程序
|
||||
通过 VIDIOC_OMAP3ISP_STAT_REQ IOCTL 操作获取时,就会产生一个
|
||||
事件。当前存在以下事件:
|
||||
|
||||
V4L2_EVENT_OMAP3ISP_AEWB
|
||||
V4L2_EVENT_OMAP3ISP_AF
|
||||
V4L2_EVENT_OMAP3ISP_HIST
|
||||
|
||||
这些 ioctl 的事件数据类型为 struct omap3isp_stat_event_status
|
||||
结构体。如果出现计算错误的统计,也同样会产生一个事件,但没有相关的统计
|
||||
数据缓冲区。这种情况下 omap3isp_stat_event_status.buf_err 会被
|
||||
设置为非零值。
|
||||
|
||||
|
||||
私有 IOCTL
|
||||
==========
|
||||
|
||||
OMAP 3 ISP 驱动支持标准的 V4L2 IOCTL 以及可能存在且实用的控制。但
|
||||
ISP 提供的许多功能都不在标准 IOCTL 之列,例如 gamma(伽马)表和统计
|
||||
数据采集配置等。
|
||||
|
||||
通常,会有一个私有 ioctl 用于配置每个包含硬件依赖功能的模块。
|
||||
|
||||
支持以下私有 IOCTL:
|
||||
|
||||
VIDIOC_OMAP3ISP_CCDC_CFG
|
||||
VIDIOC_OMAP3ISP_PRV_CFG
|
||||
VIDIOC_OMAP3ISP_AEWB_CFG
|
||||
VIDIOC_OMAP3ISP_HIST_CFG
|
||||
VIDIOC_OMAP3ISP_AF_CFG
|
||||
VIDIOC_OMAP3ISP_STAT_REQ
|
||||
VIDIOC_OMAP3ISP_STAT_EN
|
||||
|
||||
在 include/linux/omap3isp.h 中描述了这些 ioctl 使用的参数结构体。
|
||||
与特定 ISP 模块相关的 ISP 自身的详细功能在技术参考手册 (TRMs)中有
|
||||
描述,详见文档结尾。
|
||||
|
||||
虽然在不使用任何私有 IOCTL 的情况下使用 ISP 驱动是可能的,但这样无法
|
||||
获得最佳的图像质量。AEWB、AF 和 直方图(译者注:一般用于自动曝光和增益
|
||||
控制,以及图像均衡等)模块无法在未使用适当的私有 IOCTL 配置的情况下使用。
|
||||
|
||||
|
||||
CCDC 和 preview(预览)模块 IOCTL
|
||||
===============================
|
||||
|
||||
VIDIOC_OMAP3ISP_CCDC_CFG 和 VIDIOC_OMAP3ISP_PRV_CFG IOCTL
|
||||
被分别用于配置、启用和禁用 CCDC 和 preview(预览)模块的功能。在它们
|
||||
所控制的模块中,两个 IOCTL 控制多种功能。VIDIOC_OMAP3ISP_CCDC_CFG IOCTL
|
||||
接受一个指向 omap3isp_ccdc_update_config 结构体的指针作为它的参数。
|
||||
同样的,VIDIOC_OMAP3ISP_PRV_CFG 接受一个指向 omap3isp_prev_update_config
|
||||
结构体的指针。以上两个结构体定义位于 [1]。
|
||||
|
||||
这些结构体中的 update 域标识是否针对指定的功能更新配置,而 flag 域
|
||||
则标识是启用还是禁用此功能。
|
||||
|
||||
update 和 flag 位接受以下掩码值。CCDC 和 preview(预览)模块的
|
||||
每个单独功能都与一个 flag 关联(禁用或启用;在结构体中 flag 域的
|
||||
一部分)和一个指向功能配置数据的指针。
|
||||
|
||||
对于 VIDIOC_OMAP3ISP_CCDC_CFG,下面列出了 update 和 flag 域
|
||||
中的有效值。 这些值可能会在同一个 IOCTL 调用中配置多个功能。
|
||||
|
||||
OMAP3ISP_CCDC_ALAW
|
||||
OMAP3ISP_CCDC_LPF
|
||||
OMAP3ISP_CCDC_BLCLAMP
|
||||
OMAP3ISP_CCDC_BCOMP
|
||||
OMAP3ISP_CCDC_FPC
|
||||
OMAP3ISP_CCDC_CULL
|
||||
OMAP3ISP_CCDC_CONFIG_LSC
|
||||
OMAP3ISP_CCDC_TBL_LSC
|
||||
|
||||
针对 VIDIOC_OMAP3ISP_PRV_CFG 的相应值如下:
|
||||
|
||||
OMAP3ISP_PREV_LUMAENH
|
||||
OMAP3ISP_PREV_INVALAW
|
||||
OMAP3ISP_PREV_HRZ_MED
|
||||
OMAP3ISP_PREV_CFA
|
||||
OMAP3ISP_PREV_CHROMA_SUPP
|
||||
OMAP3ISP_PREV_WB
|
||||
OMAP3ISP_PREV_BLKADJ
|
||||
OMAP3ISP_PREV_RGB2RGB
|
||||
OMAP3ISP_PREV_COLOR_CONV
|
||||
OMAP3ISP_PREV_YC_LIMIT
|
||||
OMAP3ISP_PREV_DEFECT_COR
|
||||
OMAP3ISP_PREV_GAMMABYPASS
|
||||
OMAP3ISP_PREV_DRK_FRM_CAPTURE
|
||||
OMAP3ISP_PREV_DRK_FRM_SUBTRACT
|
||||
OMAP3ISP_PREV_LENS_SHADING
|
||||
OMAP3ISP_PREV_NF
|
||||
OMAP3ISP_PREV_GAMMA
|
||||
|
||||
在启用某个功能的时候,相关的配置数据指针不可为 NULL。在禁用某个功能时,
|
||||
配置数据指针会被忽略。
|
||||
|
||||
|
||||
统计模块 IOCTL
|
||||
=============
|
||||
|
||||
统计子设备相较于其他子设备提供了更多动态配置选项。在图像处理流水线处于
|
||||
工作状态时,它们可以被启用、禁用和重配。
|
||||
|
||||
统计模块总是从 CCDC 中获取输入的图像数据(由于直方图内存读取未实现)。
|
||||
统计数据可由用户通过统计子设备节点使用私有 IOCTL 获取。
|
||||
|
||||
AEWB、AF 和 直方图子设备提供的私有 IOCTL 极大程度上反应了 ISP 硬件
|
||||
提供的寄存器级接口。有些方面纯粹和驱动程序的实现相关,这些将在下面讨论。
|
||||
|
||||
VIDIOC_OMAP3ISP_STAT_EN
|
||||
-----------------------
|
||||
|
||||
这个私有 IOCTL 启用/禁用 一个统计模块。如果这个申请在视频流启动前完成,
|
||||
它将在视频流水线开始工作时生效。如果视频流水线已经处于工作状态了,它将在
|
||||
CCDC 变为空闲时生效。
|
||||
|
||||
VIDIOC_OMAP3ISP_AEWB_CFG, VIDIOC_OMAP3ISP_HIST_CFG and VIDIOC_OMAP3ISP_AF_CFG
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
这些 IOCTL 用于配置模块。它们要求用户应用程序对硬件有深入的认识。对
|
||||
大多数域的解释可以在 OMAP 的 TRM 中找到。以下两个域对于以上所有的
|
||||
私有 IOCTL 配置都很常见,由于他们没有在 TRM 中提及,故需要对其有
|
||||
更好的认识。
|
||||
|
||||
omap3isp_[h3a_af/h3a_aewb/hist]_config.buf_size:
|
||||
|
||||
模块在内部处理自身缓冲。对模块数据输出所必需的缓存大小依赖于已申请的配置。
|
||||
虽然驱动支持在视频流工作时重新配置,但对于所需缓存量大于模块启用时内部
|
||||
所分配数量的情况,则不支持重新配置。在这种情况下将返回 -EBUSY。为了避免
|
||||
此类状况,无论是禁用/重配/启用模块,还是第一次配置时申请必须的缓存大小,
|
||||
都应在模块禁用的情况下进行。
|
||||
|
||||
内部缓冲分配的大小需综合考虑所申请配置的最小缓存量以及 buf_size 域中
|
||||
所设的值。如果 buf_size 域在[minimum(最小值), maximum(最大值)]
|
||||
缓冲大小范围之外,则应该将其调整到其范围中。驱动则会选择最大值。正确的
|
||||
buf_size 值将回写到用户应用程序中。
|
||||
|
||||
omap3isp_[h3a_af/h3a_aewb/hist]_config.config_counter:
|
||||
|
||||
由于配置并未在申请之后同步生效,驱动必须提供一个跟踪这类信息的方法,
|
||||
以提供更准确的数据。在一个配置被申请之后,返回到用户空间应用程序的
|
||||
config_counter 是一个与其配置相关的唯一值。当用户应用程序接收到
|
||||
一个缓冲可用或一个新的缓冲申请事件时,这个 config_counter 用于
|
||||
一个缓冲数据和一个配置的匹配。
|
||||
|
||||
VIDIOC_OMAP3ISP_STAT_REQ
|
||||
------------------------
|
||||
|
||||
将内部缓冲队列中最早的数据发送到用户空间,然后丢弃此缓冲区。
|
||||
omap3isp_stat_data.frame_number 域与视频缓冲的 field_count
|
||||
域相匹配。
|
||||
|
||||
|
||||
技术参考手册 (TRMs) 和其他文档
|
||||
==========================
|
||||
|
||||
OMAP 3430 TRM:
|
||||
<URL:http://focus.ti.com/pdfs/wtbu/OMAP34xx_ES3.1.x_PUBLIC_TRM_vZM.zip>
|
||||
参考于 2011-03-05.
|
||||
|
||||
OMAP 35xx TRM:
|
||||
<URL:http://www.ti.com/litv/pdf/spruf98o> 参考于 2011-03-05.
|
||||
|
||||
OMAP 3630 TRM:
|
||||
<URL:http://focus.ti.com/pdfs/wtbu/OMAP36xx_ES1.x_PUBLIC_TRM_vQ.zip>
|
||||
参考于 2011-03-05.
|
||||
|
||||
DM 3730 TRM:
|
||||
<URL:http://www.ti.com/litv/pdf/sprugn4h> 参考于 2011-03-06.
|
||||
|
||||
|
||||
参考资料
|
||||
=======
|
||||
|
||||
[1] include/linux/omap3isp.h
|
||||
|
||||
[2] http://git.ideasonboard.org/?p=media-ctl.git;a=summary
|
976
Documentation/zh_CN/video4linux/v4l2-framework.txt
Normal file
976
Documentation/zh_CN/video4linux/v4l2-framework.txt
Normal file
|
@ -0,0 +1,976 @@
|
|||
Chinese translated version of Documentation/video4linux/v4l2-framework.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Mauro Carvalho Chehab <mchehab@infradead.org>
|
||||
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/video4linux/v4l2-framework.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
英文版维护者: Mauro Carvalho Chehab <mchehab@infradead.org>
|
||||
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
|
||||
|
||||
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
V4L2 驱动框架概览
|
||||
==============
|
||||
|
||||
本文档描述 V4L2 框架所提供的各种结构和它们之间的关系。
|
||||
|
||||
|
||||
介绍
|
||||
----
|
||||
|
||||
大部分现代 V4L2 设备由多个 IC 组成,在 /dev 下导出多个设备节点,
|
||||
并同时创建非 V4L2 设备(如 DVB、ALSA、FB、I2C 和红外输入设备)。
|
||||
由于这种硬件的复杂性,V4L2 驱动也变得非常复杂。
|
||||
|
||||
尤其是 V4L2 必须支持 IC 实现音视频的多路复用和编解码,这就更增加了其
|
||||
复杂性。通常这些 IC 通过一个或多个 I2C 总线连接到主桥驱动器,但也可
|
||||
使用其他总线。这些设备称为“子设备”。
|
||||
|
||||
长期以来,这个框架仅限于通过 video_device 结构体创建 V4L 设备节点,
|
||||
并使用 video_buf 处理视频缓冲(注:本文不讨论 video_buf 框架)。
|
||||
|
||||
这意味着所有驱动必须自己设置设备实例并连接到子设备。其中一部分要正确地
|
||||
完成是比较复杂的,使得许多驱动都没有正确地实现。
|
||||
|
||||
由于框架的缺失,有很多通用代码都不可重复利用。
|
||||
|
||||
因此,这个框架构建所有驱动都需要的基本结构块,而统一的框架将使通用代码
|
||||
创建成实用函数并在所有驱动中共享变得更加容易。
|
||||
|
||||
|
||||
驱动结构
|
||||
-------
|
||||
|
||||
所有 V4L2 驱动都有如下结构:
|
||||
|
||||
1) 每个设备实例的结构体--包含其设备状态。
|
||||
|
||||
2) 初始化和控制子设备的方法(如果有)。
|
||||
|
||||
3) 创建 V4L2 设备节点 (/dev/videoX、/dev/vbiX 和 /dev/radioX)
|
||||
并跟踪设备节点的特定数据。
|
||||
|
||||
4) 特定文件句柄结构体--包含每个文件句柄的数据。
|
||||
|
||||
5) 视频缓冲处理。
|
||||
|
||||
以下是它们的初略关系图:
|
||||
|
||||
device instances(设备实例)
|
||||
|
|
||||
+-sub-device instances(子设备实例)
|
||||
|
|
||||
\-V4L2 device nodes(V4L2 设备节点)
|
||||
|
|
||||
\-filehandle instances(文件句柄实例)
|
||||
|
||||
|
||||
框架结构
|
||||
-------
|
||||
|
||||
该框架非常类似驱动结构:它有一个 v4l2_device 结构用于保存设备
|
||||
实例的数据;一个 v4l2_subdev 结构体代表子设备实例;video_device
|
||||
结构体保存 V4L2 设备节点的数据;将来 v4l2_fh 结构体将跟踪文件句柄
|
||||
实例(暂未尚未实现)。
|
||||
|
||||
V4L2 框架也可与媒体框架整合(可选的)。如果驱动设置了 v4l2_device
|
||||
结构体的 mdev 域,子设备和视频节点的入口将自动出现在媒体框架中。
|
||||
|
||||
|
||||
v4l2_device 结构体
|
||||
----------------
|
||||
|
||||
每个设备实例都通过 v4l2_device (v4l2-device.h)结构体来表示。
|
||||
简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体
|
||||
嵌入到一个更大的结构体中。
|
||||
|
||||
你必须注册这个设备实例:
|
||||
|
||||
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
|
||||
|
||||
注册操作将会初始化 v4l2_device 结构体。如果 dev->driver_data 域
|
||||
为 NULL,就将其指向 v4l2_dev。
|
||||
|
||||
需要与媒体框架整合的驱动必须手动设置 dev->driver_data,指向包含
|
||||
v4l2_device 结构体实例的驱动特定设备结构体。这可以在注册 V4L2 设备
|
||||
实例前通过 dev_set_drvdata() 函数完成。同时必须设置 v4l2_device
|
||||
结构体的 mdev 域,指向适当的初始化并注册过的 media_device 实例。
|
||||
|
||||
如果 v4l2_dev->name 为空,则它将被设置为从 dev 中衍生出的值(为了
|
||||
更加精确,形式为驱动名后跟 bus_id)。如果你在调用 v4l2_device_register
|
||||
前已经设置好了,则不会被修改。如果 dev 为 NULL,则你*必须*在调用
|
||||
v4l2_device_register 前设置 v4l2_dev->name。
|
||||
|
||||
你可以基于驱动名和驱动的全局 atomic_t 类型的实例编号,通过
|
||||
v4l2_device_set_name() 设置 name。这样会生成类似 ivtv0、ivtv1 等
|
||||
名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:
|
||||
cx18-0、cx18-1 等。此函数返回实例编号。
|
||||
|
||||
第一个 “dev” 参数通常是一个指向 pci_dev、usb_interface 或
|
||||
platform_device 的指针。很少使其为 NULL,除非是一个ISA设备或者
|
||||
当一个设备创建了多个 PCI 设备,使得 v4l2_dev 无法与一个特定的父设备
|
||||
关联。
|
||||
|
||||
你也可以提供一个 notify() 回调,使子设备可以调用它实现事件通知。
|
||||
但这个设置与子设备相关。子设备支持的任何通知必须在
|
||||
include/media/<subdevice>.h 中定义一个消息头。
|
||||
|
||||
注销 v4l2_device 使用如下函数:
|
||||
|
||||
v4l2_device_unregister(struct v4l2_device *v4l2_dev);
|
||||
|
||||
如果 dev->driver_data 域指向 v4l2_dev,将会被重置为 NULL。注销同时
|
||||
会自动从设备中注销所有子设备。
|
||||
|
||||
如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。
|
||||
由于 v4l2_device 有一个指向父设备的指针必须被清除,同时标志父设备
|
||||
已消失,所以必须调用以下函数:
|
||||
|
||||
v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
|
||||
|
||||
这个函数并*不*注销子设备,因此你依然要调用 v4l2_device_unregister()
|
||||
函数。如果你的驱动器并非热插拔的,就没有必要调用 v4l2_device_disconnect()。
|
||||
|
||||
有时你需要遍历所有被特定驱动注册的设备。这通常发生在多个设备驱动使用
|
||||
同一个硬件的情况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动,
|
||||
同时 alsa 驱动也使用此硬件。
|
||||
|
||||
你可以使用如下例程遍历所有注册的设备:
|
||||
|
||||
static int callback(struct device *dev, void *p)
|
||||
{
|
||||
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);
|
||||
|
||||
/* 测试这个设备是否已经初始化 */
|
||||
if (v4l2_dev == NULL)
|
||||
return 0;
|
||||
...
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iterate(void *p)
|
||||
{
|
||||
struct device_driver *drv;
|
||||
int err;
|
||||
|
||||
/* 在PCI 总线上查找ivtv驱动。
|
||||
pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */
|
||||
drv = driver_find("ivtv", &pci_bus_type);
|
||||
/* 遍历所有的ivtv设备实例 */
|
||||
err = driver_for_each_device(drv, NULL, p, callback);
|
||||
put_driver(drv);
|
||||
return err;
|
||||
}
|
||||
|
||||
有时你需要一个设备实例的运行计数。这个通常用于映射一个设备实例到一个
|
||||
模块选择数组的索引。
|
||||
|
||||
推荐方法如下:
|
||||
|
||||
static atomic_t drv_instance = ATOMIC_INIT(0);
|
||||
|
||||
static int drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
|
||||
{
|
||||
...
|
||||
state->instance = atomic_inc_return(&drv_instance) - 1;
|
||||
}
|
||||
|
||||
如果你有多个设备节点,对于热插拔设备,知道何时注销 v4l2_device 结构体
|
||||
就比较困难。为此 v4l2_device 有引用计数支持。当调用 video_register_device
|
||||
时增加引用计数,而设备节点释放时减小引用计数。当引用计数为零,则
|
||||
v4l2_device 的release() 回调将被执行。你就可以在此时做最后的清理工作。
|
||||
|
||||
如果创建了其他设备节点(比如 ALSA),则你可以通过以下函数手动增减
|
||||
引用计数:
|
||||
|
||||
void v4l2_device_get(struct v4l2_device *v4l2_dev);
|
||||
|
||||
或:
|
||||
|
||||
int v4l2_device_put(struct v4l2_device *v4l2_dev);
|
||||
|
||||
由于引用技术初始化为 1 ,你也需要在 disconnect() 回调(对于 USB 设备)中
|
||||
调用 v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),否则
|
||||
引用计数将永远不会为 0 。
|
||||
|
||||
v4l2_subdev结构体
|
||||
------------------
|
||||
|
||||
许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责
|
||||
音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。
|
||||
|
||||
这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的
|
||||
统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。
|
||||
|
||||
每个子设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体可以单独
|
||||
代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态
|
||||
信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了
|
||||
内核创建的设备数据。建议使用 v4l2_set_subdevdata() 将这个结构体的
|
||||
指针保存在 v4l2_subdev 的私有数据域(dev_priv)中。这使得通过 v4l2_subdev
|
||||
找到实际的低层总线特定设备数据变得容易。
|
||||
|
||||
你同时需要一个从低层结构体获取 v4l2_subdev 指针的方法。对于常用的
|
||||
i2c_client 结构体,i2c_set_clientdata() 函数可用于保存一个 v4l2_subdev
|
||||
指针;对于其他总线你可能需要使用其他相关函数。
|
||||
|
||||
桥驱动中也应保存每个子设备的私有数据,比如一个指向特定桥的各设备私有
|
||||
数据的指针。为此 v4l2_subdev 结构体提供主机私有数据域(host_priv),
|
||||
并可通过 v4l2_get_subdev_hostdata() 和 v4l2_set_subdev_hostdata()
|
||||
访问。
|
||||
|
||||
从总线桥驱动的视角,驱动加载子设备模块并以某种方式获得 v4l2_subdev
|
||||
结构体指针。对于 i2c 总线设备相对简单:调用 i2c_get_clientdata()。
|
||||
对于其他总线也需要做类似的操作。针对 I2C 总线上的子设备辅助函数帮你
|
||||
完成了大部分复杂的工作。
|
||||
|
||||
每个 v4l2_subdev 都包含子设备驱动需要实现的函数指针(如果对此设备
|
||||
不适用,可为NULL)。由于子设备可完成许多不同的工作,而在一个庞大的
|
||||
函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以,
|
||||
函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体。
|
||||
|
||||
顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动
|
||||
不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。
|
||||
|
||||
这些结构体定义如下:
|
||||
|
||||
struct v4l2_subdev_core_ops {
|
||||
int (*log_status)(struct v4l2_subdev *sd);
|
||||
int (*init)(struct v4l2_subdev *sd, u32 val);
|
||||
...
|
||||
};
|
||||
|
||||
struct v4l2_subdev_tuner_ops {
|
||||
...
|
||||
};
|
||||
|
||||
struct v4l2_subdev_audio_ops {
|
||||
...
|
||||
};
|
||||
|
||||
struct v4l2_subdev_video_ops {
|
||||
...
|
||||
};
|
||||
|
||||
struct v4l2_subdev_pad_ops {
|
||||
...
|
||||
};
|
||||
|
||||
struct v4l2_subdev_ops {
|
||||
const struct v4l2_subdev_core_ops *core;
|
||||
const struct v4l2_subdev_tuner_ops *tuner;
|
||||
const struct v4l2_subdev_audio_ops *audio;
|
||||
const struct v4l2_subdev_video_ops *video;
|
||||
const struct v4l2_subdev_pad_ops *video;
|
||||
};
|
||||
|
||||
其中 core(核心)函数集通常可用于所有子设备,其他类别的实现依赖于
|
||||
子设备。如视频设备可能不支持音频操作函数,反之亦然。
|
||||
|
||||
这样的设置在限制了函数指针数量的同时,还使增加新的操作函数和分类
|
||||
变得较为容易。
|
||||
|
||||
子设备驱动可使用如下函数初始化 v4l2_subdev 结构体:
|
||||
|
||||
v4l2_subdev_init(sd, &ops);
|
||||
|
||||
然后,你必须用一个唯一的名字初始化 subdev->name,并初始化模块的
|
||||
owner 域。若使用 i2c 辅助函数,这些都会帮你处理好。
|
||||
|
||||
若需同媒体框架整合,你必须调用 media_entity_init() 初始化 v4l2_subdev
|
||||
结构体中的 media_entity 结构体(entity 域):
|
||||
|
||||
struct media_pad *pads = &my_sd->pads;
|
||||
int err;
|
||||
|
||||
err = media_entity_init(&sd->entity, npads, pads, 0);
|
||||
|
||||
pads 数组必须预先初始化。无须手动设置 media_entity 的 type 和
|
||||
name 域,但如有必要,revision 域必须初始化。
|
||||
|
||||
当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。
|
||||
|
||||
在子设备被注销之后,不要忘记清理 media_entity 结构体:
|
||||
|
||||
media_entity_cleanup(&sd->entity);
|
||||
|
||||
如果子设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops
|
||||
替代 v4l2_subdev_video_ops 实现格式相关的功能。
|
||||
|
||||
这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接
|
||||
验证函数。链接验证函数应对管道(两端链接的都是 V4L2 子设备)中的每个
|
||||
链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。
|
||||
|
||||
如果 link_validate 操作没有设置,默认的 v4l2_subdev_link_validate_default()
|
||||
函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端
|
||||
都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行
|
||||
上面提到的检查。
|
||||
|
||||
设备(桥)驱动程序必须向 v4l2_device 注册 v4l2_subdev:
|
||||
|
||||
int err = v4l2_device_register_subdev(v4l2_dev, sd);
|
||||
|
||||
如果子设备模块在它注册前消失,这个操作可能失败。在这个函数成功返回后,
|
||||
subdev->dev 域就指向了 v4l2_device。
|
||||
|
||||
如果 v4l2_device 父设备的 mdev 域为非 NULL 值,则子设备实体将被自动
|
||||
注册为媒体设备。
|
||||
|
||||
注销子设备则可用如下函数:
|
||||
|
||||
v4l2_device_unregister_subdev(sd);
|
||||
|
||||
此后,子设备模块就可卸载,且 sd->dev == NULL。
|
||||
|
||||
注册之设备后,可通过以下方式直接调用其操作函数:
|
||||
|
||||
err = sd->ops->core->g_std(sd, &norm);
|
||||
|
||||
但使用如下宏会比较容易且合适:
|
||||
|
||||
err = v4l2_subdev_call(sd, core, g_std, &norm);
|
||||
|
||||
这个宏将会做 NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果
|
||||
subdev->core 或 subdev->core->g_std 为 NULL,则返回 -ENOIOCTLCMD;
|
||||
否则将返回 subdev->ops->core->g_std ops 调用的实际结果。
|
||||
|
||||
有时也可能同时调用所有或一系列子设备的某个操作函数:
|
||||
|
||||
v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm);
|
||||
|
||||
任何不支持此操作的子设备都会被跳过,并忽略错误返回值。但如果你需要
|
||||
检查出错码,则可使用如下函数:
|
||||
|
||||
err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm);
|
||||
|
||||
除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD
|
||||
外)没有错误发生,则返回 0。
|
||||
|
||||
对于以上两个函数的第二个参数为组 ID。如果为 0,则所有子设备都会执行
|
||||
这个操作。如果为非 0 值,则只有那些组 ID 匹配的子设备才会执行此操作。
|
||||
在桥驱动注册一个子设备前,可以设置 sd->grp_id 为任何期望值(默认值为
|
||||
0)。这个值属于桥驱动,且子设备驱动将不会修改和使用它。
|
||||
|
||||
组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路板上有多个
|
||||
音频芯片,每个都有改变音量的能力。但当用户想要改变音量的时候,通常
|
||||
只有一个会被实际使用。你可以对这样的子设备设置组 ID 为(例如 AUDIO_CONTROLLER)
|
||||
并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就保证了只有
|
||||
需要的子设备才会执行这个回调。
|
||||
|
||||
如果子设备需要通知它的 v4l2_device 父设备一个事件,可以调用
|
||||
v4l2_subdev_notify(sd, notification, arg)。这个宏检查是否有一个
|
||||
notify() 回调被注册,如果没有,返回 -ENODEV。否则返回 notify() 调用
|
||||
结果。
|
||||
|
||||
使用 v4l2_subdev 的好处在于它是一个通用结构体,且不包含任何底层硬件
|
||||
信息。所有驱动可以包含多个 I2C 总线的子设备,但也有子设备是通过 GPIO
|
||||
控制。这个区别仅在配置设备时有关系,一旦子设备注册完成,对于 v4l2
|
||||
子系统来说就完全透明了。
|
||||
|
||||
|
||||
V4L2 子设备用户空间API
|
||||
--------------------
|
||||
|
||||
除了通过 v4l2_subdev_ops 结构导出的内核 API,V4L2 子设备也可以直接
|
||||
通过用户空间应用程序来控制。
|
||||
|
||||
可以在 /dev 中创建名为 v4l-subdevX 设备节点,以通过其直接访问子设备。
|
||||
如果子设备支持用户空间直接配置,必须在注册前设置 V4L2_SUBDEV_FL_HAS_DEVNODE
|
||||
标志。
|
||||
|
||||
注册子设备之后, v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes()
|
||||
函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE 的子设备创建
|
||||
设备节点。这些设备节点会在子设备注销时自动删除。
|
||||
|
||||
这些设备节点处理 V4L2 API 的一个子集。
|
||||
|
||||
VIDIOC_QUERYCTRL
|
||||
VIDIOC_QUERYMENU
|
||||
VIDIOC_G_CTRL
|
||||
VIDIOC_S_CTRL
|
||||
VIDIOC_G_EXT_CTRLS
|
||||
VIDIOC_S_EXT_CTRLS
|
||||
VIDIOC_TRY_EXT_CTRLS
|
||||
|
||||
这些 ioctls 控制与 V4L2 中定义的一致。他们行为相同,唯一的
|
||||
不同是他们只处理子设备的控制实现。根据驱动程序,这些控制也
|
||||
可以通过一个(或多个) V4L2 设备节点访问。
|
||||
|
||||
VIDIOC_DQEVENT
|
||||
VIDIOC_SUBSCRIBE_EVENT
|
||||
VIDIOC_UNSUBSCRIBE_EVENT
|
||||
|
||||
这些 ioctls 事件与 V4L2 中定义的一致。他们行为相同,唯一的
|
||||
不同是他们只处理子设备产生的事件。根据驱动程序,这些事件也
|
||||
可以通过一个(或多个) V4L2 设备节点上报。
|
||||
|
||||
要使用事件通知的子设备驱动,在注册子设备前必须在 v4l2_subdev::flags
|
||||
中设置 V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents
|
||||
中初始化事件队列深度。注册完成后,事件会在 v4l2_subdev::devnode
|
||||
设备节点中像通常一样被排队。
|
||||
|
||||
为正确支持事件机制,poll() 文件操作也应被实现。
|
||||
|
||||
私有 ioctls
|
||||
|
||||
不在以上列表中的所有 ioctls 会通过 core::ioctl 操作直接传递
|
||||
给子设备驱动。
|
||||
|
||||
|
||||
I2C 子设备驱动
|
||||
-------------
|
||||
|
||||
由于这些驱动很常见,所以内特提供了特定的辅助函数(v4l2-common.h)让这些
|
||||
设备的使用更加容易。
|
||||
|
||||
添加 v4l2_subdev 支持的推荐方法是让 I2C 驱动将 v4l2_subdev 结构体
|
||||
嵌入到为每个 I2C 设备实例创建的状态结构体中。而最简单的设备没有状态
|
||||
结构体,此时可以直接创建一个 v4l2_subdev 结构体。
|
||||
|
||||
一个典型的状态结构体如下所示(‘chipname’用芯片名代替):
|
||||
|
||||
struct chipname_state {
|
||||
struct v4l2_subdev sd;
|
||||
... /* 附加的状态域*/
|
||||
};
|
||||
|
||||
初始化 v4l2_subdev 结构体的方法如下:
|
||||
|
||||
v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);
|
||||
|
||||
这个函数将填充 v4l2_subdev 结构体中的所有域,并保证 v4l2_subdev 和
|
||||
i2c_client 都指向彼此。
|
||||
|
||||
同时,你也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针
|
||||
添加一个辅助内联函数。
|
||||
|
||||
static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
|
||||
{
|
||||
return container_of(sd, struct chipname_state, sd);
|
||||
}
|
||||
|
||||
使用以下函数可以通过 v4l2_subdev 结构体指针获得 i2c_client 结构体
|
||||
指针:
|
||||
|
||||
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
||||
|
||||
而以下函数则相反,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体
|
||||
指针:
|
||||
|
||||
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||||
|
||||
当 remove()函数被调用前,必须保证先调用 v4l2_device_unregister_subdev(sd)。
|
||||
此操作将会从桥驱动中注销子设备。即使子设备没有注册,调用此函数也是
|
||||
安全的。
|
||||
|
||||
必须这样做的原因是:当桥驱动注销 i2c 适配器时,remove()回调函数
|
||||
会被那个适配器上的 i2c 设备调用。此后,相应的 v4l2_subdev 结构体
|
||||
就不存在了,所有它们必须先被注销。在 remove()回调函数中调用
|
||||
v4l2_device_unregister_subdev(sd),可以保证执行总是正确的。
|
||||
|
||||
|
||||
桥驱动也有一些辅组函数可用:
|
||||
|
||||
struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
|
||||
"module_foo", "chipid", 0x36, NULL);
|
||||
|
||||
这个函数会加载给定的模块(如果没有模块需要加载,可以为 NULL),
|
||||
并用给定的 i2c 适配器结构体指针(i2c_adapter)和 器件地址(chip/address)
|
||||
作为参数调用 i2c_new_device()。如果一切顺利,则就在 v4l2_device
|
||||
中注册了子设备。
|
||||
|
||||
你也可以利用 v4l2_i2c_new_subdev()的最后一个参数,传递一个可能的
|
||||
I2C 地址数组,让函数自动探测。这些探测地址只有在前一个参数为 0 的
|
||||
情况下使用。非零参数意味着你知道准确的 i2c 地址,所以此时无须进行
|
||||
探测。
|
||||
|
||||
如果出错,两个函数都返回 NULL。
|
||||
|
||||
注意:传递给 v4l2_i2c_new_subdev()的 chipid 通常与模块名一致。
|
||||
它允许你指定一个芯片的变体,比如“saa7114”或“saa7115”。一般通过
|
||||
i2c 驱动自动探测。chipid 的使用是在今后需要深入了解的事情。这个与
|
||||
i2c 驱动不同,较容易混淆。要知道支持哪些芯片变体,你可以查阅 i2c
|
||||
驱动代码的 i2c_device_id 表,上面列出了所有可能支持的芯片。
|
||||
|
||||
还有两个辅助函数:
|
||||
|
||||
v4l2_i2c_new_subdev_cfg:这个函数添加新的 irq 和 platform_data
|
||||
参数,并有‘addr’和‘probed_addrs’参数:如果 addr 非零,则被使用
|
||||
(不探测变体),否则 probed_addrs 中的地址将用于自动探测。
|
||||
|
||||
例如:以下代码将会探测地址(0x10):
|
||||
|
||||
struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter,
|
||||
"module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10));
|
||||
|
||||
v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其
|
||||
替代 irq、platform_data 和 add r参数传递给 i2c 驱动。
|
||||
|
||||
如果子设备支持 s_config 核心操作,这个操作会在子设备配置好之后以 irq 和
|
||||
platform_data 为参数调用。早期的 v4l2_i2c_new_(probed_)subdev 函数
|
||||
同样也会调用 s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。
|
||||
|
||||
video_device结构体
|
||||
-----------------
|
||||
|
||||
在 /dev 目录下的实际设备节点根据 video_device 结构体(v4l2-dev.h)
|
||||
创建。此结构体既可以动态分配也可以嵌入到一个更大的结构体中。
|
||||
|
||||
动态分配方法如下:
|
||||
|
||||
struct video_device *vdev = video_device_alloc();
|
||||
|
||||
if (vdev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
vdev->release = video_device_release;
|
||||
|
||||
如果将其嵌入到一个大结构体中,则必须自己实现 release()回调。
|
||||
|
||||
struct video_device *vdev = &my_vdev->vdev;
|
||||
|
||||
vdev->release = my_vdev_release;
|
||||
|
||||
release()回调必须被设置,且在最后一个 video_device 用户退出之后
|
||||
被调用。
|
||||
|
||||
默认的 video_device_release()回调只是调用 kfree 来释放之前分配的
|
||||
内存。
|
||||
|
||||
你应该设置这些域:
|
||||
|
||||
- v4l2_dev: 设置为 v4l2_device 父设备。
|
||||
|
||||
- name: 设置为唯一的描述性设备名。
|
||||
|
||||
- fops: 设置为已有的 v4l2_file_operations 结构体。
|
||||
|
||||
- ioctl_ops: 如果你使用v4l2_ioctl_ops 来简化 ioctl 的维护
|
||||
(强烈建议使用,且将来可能变为强制性的!),然后设置你自己的
|
||||
v4l2_ioctl_ops 结构体.
|
||||
|
||||
- lock: 如果你要在驱动中实现所有的锁操作,则设为 NULL 。否则
|
||||
就要设置一个指向 struct mutex_lock 结构体的指针,这个锁将
|
||||
在 unlocked_ioctl 文件操作被调用前由内核获得,并在调用返回后
|
||||
释放。详见下一节。
|
||||
|
||||
- prio: 保持对优先级的跟踪。用于实现 VIDIOC_G/S_PRIORITY。如果
|
||||
设置为 NULL,则会使用 v4l2_device 中的 v4l2_prio_state 结构体。
|
||||
如果要对每个设备节点(组)实现独立的优先级,可以将其指向自己
|
||||
实现的 v4l2_prio_state 结构体。
|
||||
|
||||
- parent: 仅在使用 NULL 作为父设备结构体参数注册 v4l2_device 时
|
||||
设置此参数。只有在一个硬件设备包含多一个 PCI 设备,共享同一个
|
||||
v4l2_device 核心时才会发生。
|
||||
|
||||
cx88 驱动就是一个例子:一个 v4l2_device 结构体核心,被一个裸的
|
||||
视频 PCI 设备(cx8800)和一个 MPEG PCI 设备(cx8802)共用。由于
|
||||
v4l2_device 无法与特定的 PCI 设备关联,所有没有设置父设备。但当
|
||||
video_device 配置后,就知道使用哪个父 PCI 设备了。
|
||||
|
||||
如果你使用 v4l2_ioctl_ops,则应该在 v4l2_file_operations 结构体中
|
||||
设置 .unlocked_ioctl 指向 video_ioctl2。
|
||||
|
||||
请勿使用 .ioctl!它已被废弃,今后将消失。
|
||||
|
||||
某些情况下你要告诉核心:你在 v4l2_ioctl_ops 指定的某个函数应被忽略。
|
||||
你可以在 video_device_register 被调用前通过以下函数标记这个 ioctls。
|
||||
|
||||
void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd);
|
||||
|
||||
基于外部因素(例如某个板卡已被使用),在不创建新结构体的情况下,你想
|
||||
要关闭 v4l2_ioctl_ops 中某个特性往往需要这个机制。
|
||||
|
||||
v4l2_file_operations 结构体是 file_operations 的一个子集。其主要
|
||||
区别在于:因 inode 参数从未被使用,它将被忽略。
|
||||
|
||||
如果需要与媒体框架整合,你必须通过调用 media_entity_init() 初始化
|
||||
嵌入在 video_device 结构体中的 media_entity(entity 域)结构体:
|
||||
|
||||
struct media_pad *pad = &my_vdev->pad;
|
||||
int err;
|
||||
|
||||
err = media_entity_init(&vdev->entity, 1, pad, 0);
|
||||
|
||||
pads 数组必须预先初始化。没有必要手动设置 media_entity 的 type 和
|
||||
name 域。
|
||||
|
||||
当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。
|
||||
|
||||
v4l2_file_operations 与锁
|
||||
--------------------------
|
||||
|
||||
你可以在 video_device 结构体中设置一个指向 mutex_lock 的指针。通常
|
||||
这既可是一个顶层互斥锁也可为设备节点自身的互斥锁。默认情况下,此锁
|
||||
用于 unlocked_ioctl,但为了使用 ioctls 你通过以下函数可禁用锁定:
|
||||
|
||||
void v4l2_disable_ioctl_locking(struct video_device *vdev, unsigned int cmd);
|
||||
|
||||
例如: v4l2_disable_ioctl_locking(vdev, VIDIOC_DQBUF);
|
||||
|
||||
你必须在注册 video_device 前调用这个函数。
|
||||
|
||||
特别是对于 USB 驱动程序,某些命令(如设置控制)需要很长的时间,可能
|
||||
需要自行为缓冲区队列的 ioctls 实现锁定。
|
||||
|
||||
如果你需要更细粒度的锁,你必须设置 mutex_lock 为 NULL,并完全自己实现
|
||||
锁机制。
|
||||
|
||||
这完全由驱动开发者决定使用何种方法。然而,如果你的驱动存在长延时操作
|
||||
(例如,改变 USB 摄像头的曝光时间可能需要较长时间),而你又想让用户
|
||||
在等待长延时操作完成期间做其他的事,则你最好自己实现锁机制。
|
||||
|
||||
如果指定一个锁,则所有 ioctl 操作将在这个锁的作用下串行执行。如果你
|
||||
使用 videobuf,则必须将同一个锁传递给 videobuf 队列初始化函数;如
|
||||
videobuf 必须等待一帧的到达,则可临时解锁并在这之后重新上锁。如果驱动
|
||||
也在代码执行期间等待,则可做同样的工作(临时解锁,再上锁)让其他进程
|
||||
可以在第一个进程阻塞时访问设备节点。
|
||||
|
||||
在使用 videobuf2 的情况下,必须实现 wait_prepare 和 wait_finish 回调
|
||||
在适当的时候解锁/加锁。进一步来说,如果你在 video_device 结构体中使用
|
||||
锁,则必须在 wait_prepare 和 wait_finish 中对这个互斥锁进行解锁/加锁。
|
||||
|
||||
热插拔的断开实现也必须在调用 v4l2_device_disconnect 前获得锁。
|
||||
|
||||
video_device注册
|
||||
---------------
|
||||
|
||||
接下来你需要注册视频设备:这会为你创建一个字符设备。
|
||||
|
||||
err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
|
||||
if (err) {
|
||||
video_device_release(vdev); /* or kfree(my_vdev); */
|
||||
return err;
|
||||
}
|
||||
|
||||
如果 v4l2_device 父设备的 mdev 域为非 NULL 值,视频设备实体将自动
|
||||
注册为媒体设备。
|
||||
|
||||
注册哪种设备是根据类型(type)参数。存在以下类型:
|
||||
|
||||
VFL_TYPE_GRABBER: 用于视频输入/输出设备的 videoX
|
||||
VFL_TYPE_VBI: 用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视)
|
||||
VFL_TYPE_RADIO: 用于广播调谐器的 radioX
|
||||
|
||||
最后一个参数让你确定一个所控制设备的设备节点号数量(例如 videoX 中的 X)。
|
||||
通常你可以传入-1,让 v4l2 框架自己选择第一个空闲的编号。但是有时用户
|
||||
需要选择一个特定的节点号。驱动允许用户通过驱动模块参数选择一个特定的
|
||||
设备节点号是很普遍的。这个编号将会传递给这个函数,且 video_register_device
|
||||
将会试图选择这个设备节点号。如果这个编号被占用,下一个空闲的设备节点
|
||||
编号将被选中,并向内核日志中发送一个警告信息。
|
||||
|
||||
另一个使用场景是当驱动创建多个设备时。这种情况下,对不同的视频设备在
|
||||
编号上使用不同的范围是很有用的。例如,视频捕获设备从 0 开始,视频
|
||||
输出设备从 16 开始。所以你可以使用最后一个参数来指定设备节点号最小值,
|
||||
而 v4l2 框架会试图选择第一个的空闲编号(等于或大于你提供的编号)。
|
||||
如果失败,则它会就选择第一个空闲的编号。
|
||||
|
||||
由于这种情况下,你会忽略无法选择特定设备节点号的警告,则可调用
|
||||
video_register_device_no_warn() 函数避免警告信息的产生。
|
||||
|
||||
只要设备节点被创建,一些属性也会同时创建。在 /sys/class/video4linux
|
||||
目录中你会找到这些设备。例如进入其中的 video0 目录,你会看到‘name’和
|
||||
‘index’属性。‘name’属性值就是 video_device 结构体中的‘name’域。
|
||||
|
||||
‘index’属性值就是设备节点的索引值:每次调用 video_register_device(),
|
||||
索引值都递增 1 。第一个视频设备节点总是从索引值 0 开始。
|
||||
|
||||
用户可以设置 udev 规则,利用索引属性生成花哨的设备名(例如:用‘mpegX’
|
||||
代表 MPEG 视频捕获设备节点)。
|
||||
|
||||
在设备成功注册后,就可以使用这些域:
|
||||
|
||||
- vfl_type: 传递给 video_register_device 的设备类型。
|
||||
- minor: 已指派的次设备号。
|
||||
- num: 设备节点编号 (例如 videoX 中的 X)。
|
||||
- index: 设备索引号。
|
||||
|
||||
如果注册失败,你必须调用 video_device_release() 来释放已分配的
|
||||
video_device 结构体;如果 video_device 是嵌入在自己创建的结构体中,
|
||||
你也必须释放它。vdev->release() 回调不会在注册失败之后被调用,
|
||||
你也不应试图在注册失败后注销设备。
|
||||
|
||||
|
||||
video_device 注销
|
||||
----------------
|
||||
|
||||
当视频设备节点已被移除,不论是卸载驱动还是USB设备断开,你都应注销
|
||||
它们:
|
||||
|
||||
video_unregister_device(vdev);
|
||||
|
||||
这个操作将从 sysfs 中移除设备节点(导致 udev 将其从 /dev 中移除)。
|
||||
|
||||
video_unregister_device() 返回之后,就无法完成打开操作。尽管如此,
|
||||
USB 设备的情况则不同,某些应用程序可能依然打开着其中一个已注销设备
|
||||
节点。所以在注销之后,所有文件操作(当然除了 release )也应返回错误值。
|
||||
|
||||
当最后一个视频设备节点的用户退出,则 vdev->release() 回调会被调用,
|
||||
并且你可以做最后的清理操作。
|
||||
|
||||
不要忘记清理与视频设备相关的媒体入口(如果被初始化过):
|
||||
|
||||
media_entity_cleanup(&vdev->entity);
|
||||
|
||||
这可以在 release 回调中完成。
|
||||
|
||||
|
||||
video_device 辅助函数
|
||||
---------------------
|
||||
|
||||
一些有用的辅助函数如下:
|
||||
|
||||
- file/video_device 私有数据
|
||||
|
||||
你可以用以下函数在 video_device 结构体中设置/获取驱动私有数据:
|
||||
|
||||
void *video_get_drvdata(struct video_device *vdev);
|
||||
void video_set_drvdata(struct video_device *vdev, void *data);
|
||||
|
||||
注意:在调用 video_register_device() 前执行 video_set_drvdata()
|
||||
是安全的。
|
||||
|
||||
而以下函数:
|
||||
|
||||
struct video_device *video_devdata(struct file *file);
|
||||
|
||||
返回 file 结构体中拥有的的 video_device 指针。
|
||||
|
||||
video_drvdata 辅助函数结合了 video_get_drvdata 和 video_devdata
|
||||
的功能:
|
||||
|
||||
void *video_drvdata(struct file *file);
|
||||
|
||||
你可以使用如下代码从 video_device 结构体中获取 v4l2_device 结构体
|
||||
指针:
|
||||
|
||||
struct v4l2_device *v4l2_dev = vdev->v4l2_dev;
|
||||
|
||||
- 设备节点名
|
||||
|
||||
video_device 设备节点在内核中的名称可以通过以下函数获得
|
||||
|
||||
const char *video_device_node_name(struct video_device *vdev);
|
||||
|
||||
这个名字被用户空间工具(例如 udev)作为提示信息使用。应尽可能使用
|
||||
此功能,而非访问 video_device::num 和 video_device::minor 域。
|
||||
|
||||
|
||||
视频缓冲辅助函数
|
||||
---------------
|
||||
|
||||
v4l2 核心 API 提供了一个处理视频缓冲的标准方法(称为“videobuf”)。
|
||||
这些方法使驱动可以通过统一的方式实现 read()、mmap() 和 overlay()。
|
||||
目前在设备上支持视频缓冲的方法有分散/聚集 DMA(videobuf-dma-sg)、
|
||||
线性 DMA(videobuf-dma-contig)以及大多用于 USB 设备的用 vmalloc
|
||||
分配的缓冲(videobuf-vmalloc)。
|
||||
|
||||
请参阅 Documentation/video4linux/videobuf,以获得更多关于 videobuf
|
||||
层的使用信息。
|
||||
|
||||
v4l2_fh 结构体
|
||||
-------------
|
||||
|
||||
v4l2_fh 结构体提供一个保存用于 V4L2 框架的文件句柄特定数据的简单方法。
|
||||
如果 video_device 标志,新驱动
|
||||
必须使用 v4l2_fh 结构体,因为它也用于实现优先级处理(VIDIOC_G/S_PRIORITY)。
|
||||
|
||||
v4l2_fh 的用户(位于 V4l2 框架中,并非驱动)可通过测试
|
||||
video_device->flags 中的 V4L2_FL_USES_V4L2_FH 位得知驱动是否使用
|
||||
v4l2_fh 作为他的 file->private_data 指针。这个位会在调用 v4l2_fh_init()
|
||||
时被设置。
|
||||
|
||||
v4l2_fh 结构体作为驱动自身文件句柄结构体的一部分被分配,且驱动在
|
||||
其打开函数中将 file->private_data 指向它。
|
||||
|
||||
在许多情况下,v4l2_fh 结构体会嵌入到一个更大的结构体中。这钟情况下,
|
||||
应该在 open() 中调用 v4l2_fh_init+v4l2_fh_add,并在 release() 中
|
||||
调用 v4l2_fh_del+v4l2_fh_exit。
|
||||
|
||||
驱动可以通过使用 container_of 宏提取他们自己的文件句柄结构体。例如:
|
||||
|
||||
struct my_fh {
|
||||
int blah;
|
||||
struct v4l2_fh fh;
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
int my_open(struct file *file)
|
||||
{
|
||||
struct my_fh *my_fh;
|
||||
struct video_device *vfd;
|
||||
int ret;
|
||||
|
||||
...
|
||||
|
||||
my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);
|
||||
|
||||
...
|
||||
|
||||
v4l2_fh_init(&my_fh->fh, vfd);
|
||||
|
||||
...
|
||||
|
||||
file->private_data = &my_fh->fh;
|
||||
v4l2_fh_add(&my_fh->fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int my_release(struct file *file)
|
||||
{
|
||||
struct v4l2_fh *fh = file->private_data;
|
||||
struct my_fh *my_fh = container_of(fh, struct my_fh, fh);
|
||||
|
||||
...
|
||||
v4l2_fh_del(&my_fh->fh);
|
||||
v4l2_fh_exit(&my_fh->fh);
|
||||
kfree(my_fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
以下是 v4l2_fh 函数使用的简介:
|
||||
|
||||
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev)
|
||||
|
||||
初始化文件句柄。这*必须*在驱动的 v4l2_file_operations->open()
|
||||
函数中执行。
|
||||
|
||||
void v4l2_fh_add(struct v4l2_fh *fh)
|
||||
|
||||
添加一个 v4l2_fh 到 video_device 文件句柄列表。一旦文件句柄
|
||||
初始化完成就必须调用。
|
||||
|
||||
void v4l2_fh_del(struct v4l2_fh *fh)
|
||||
|
||||
从 video_device() 中解除文件句柄的关联。文件句柄的退出函数也
|
||||
将被调用。
|
||||
|
||||
void v4l2_fh_exit(struct v4l2_fh *fh)
|
||||
|
||||
清理文件句柄。在清理完 v4l2_fh 后,相关内存会被释放。
|
||||
|
||||
|
||||
如果 v4l2_fh 不是嵌入在其他结构体中的,则可以用这些辅助函数:
|
||||
|
||||
int v4l2_fh_open(struct file *filp)
|
||||
|
||||
分配一个 v4l2_fh 结构体空间,初始化并将其添加到 file 结构体相关的
|
||||
video_device 结构体中。
|
||||
|
||||
int v4l2_fh_release(struct file *filp)
|
||||
|
||||
从 file 结构体相关的 video_device 结构体中删除 v4l2_fh ,清理
|
||||
v4l2_fh 并释放空间。
|
||||
|
||||
这两个函数可以插入到 v4l2_file_operation 的 open() 和 release()
|
||||
操作中。
|
||||
|
||||
|
||||
某些驱动需要在第一个文件句柄打开和最后一个文件句柄关闭的时候做些
|
||||
工作。所以加入了两个辅助函数以检查 v4l2_fh 结构体是否是相关设备
|
||||
节点打开的唯一文件句柄。
|
||||
|
||||
int v4l2_fh_is_singular(struct v4l2_fh *fh)
|
||||
|
||||
如果此文件句柄是唯一打开的文件句柄,则返回 1 ,否则返回 0 。
|
||||
|
||||
int v4l2_fh_is_singular_file(struct file *filp)
|
||||
|
||||
功能相同,但通过 filp->private_data 调用 v4l2_fh_is_singular。
|
||||
|
||||
|
||||
V4L2 事件机制
|
||||
-----------
|
||||
|
||||
V4L2 事件机制提供了一个通用的方法将事件传递到用户空间。驱动必须使用
|
||||
v4l2_fh 才能支持 V4L2 事件机制。
|
||||
|
||||
|
||||
事件通过一个类型和选择 ID 来定义。ID 对应一个 V4L2 对象,例如
|
||||
一个控制 ID。如果未使用,则 ID 为 0。
|
||||
|
||||
当用户订阅一个事件,驱动会为此分配一些 kevent 结构体。所以每个
|
||||
事件组(类型、ID)都会有自己的一套 kevent 结构体。这保证了如果
|
||||
一个驱动短时间内产生了许多同类事件,不会覆盖其他类型的事件。
|
||||
|
||||
但如果你收到的事件数量大于同类事件 kevent 的保存数量,则最早的
|
||||
事件将被丢弃,并加入新事件。
|
||||
|
||||
此外,v4l2_subscribed_event 结构体内部有可供驱动设置的 merge() 和
|
||||
replace() 回调,这些回调会在新事件产生且没有多余空间的时候被调用。
|
||||
replace() 回调让你可以将早期事件的净荷替换为新事件的净荷,将早期
|
||||
净荷的相关数据合并到替换进来的新净荷中。当该类型的事件仅分配了一个
|
||||
kevent 结构体时,它将被调用。merge() 回调让你可以合并最早的事件净荷
|
||||
到在它之后的那个事件净荷中。当该类型的事件分配了两个或更多 kevent
|
||||
结构体时,它将被调用。
|
||||
|
||||
这种方法不会有状态信息丢失,只会导致中间步骤信息丢失。
|
||||
|
||||
|
||||
关于 replace/merge 回调的一个不错的例子在 v4l2-event.c 中:用于
|
||||
控制事件的 ctrls_replace() 和 ctrls_merge() 回调。
|
||||
|
||||
注意:这些回调可以在中断上下文中调用,所以它们必须尽快完成并退出。
|
||||
|
||||
有用的函数:
|
||||
|
||||
void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
|
||||
|
||||
将事件加入视频设备的队列。驱动仅负责填充 type 和 data 域。
|
||||
其他域由 V4L2 填充。
|
||||
|
||||
int v4l2_event_subscribe(struct v4l2_fh *fh,
|
||||
struct v4l2_event_subscription *sub, unsigned elems,
|
||||
const struct v4l2_subscribed_event_ops *ops)
|
||||
|
||||
video_device->ioctl_ops->vidioc_subscribe_event 必须检测驱动能
|
||||
产生特定 id 的事件。然后调用 v4l2_event_subscribe() 来订阅该事件。
|
||||
|
||||
elems 参数是该事件的队列大小。若为 0,V4L2 框架将会(根据事件类型)
|
||||
填充默认值。
|
||||
|
||||
ops 参数允许驱动指定一系列回调:
|
||||
* add: 当添加一个新监听者时调用(重复订阅同一个事件,此回调
|
||||
仅被执行一次)。
|
||||
* del: 当一个监听者停止监听时调用。
|
||||
* replace: 用‘新’事件替换‘早期‘事件。
|
||||
* merge: 将‘早期‘事件合并到‘新’事件中。
|
||||
这四个调用都是可选的,如果不想指定任何回调,则 ops 可为 NULL。
|
||||
|
||||
int v4l2_event_unsubscribe(struct v4l2_fh *fh,
|
||||
struct v4l2_event_subscription *sub)
|
||||
|
||||
v4l2_ioctl_ops 结构体中的 vidioc_unsubscribe_event 回调函数。
|
||||
驱动程序可以直接使用 v4l2_event_unsubscribe() 实现退订事件过程。
|
||||
|
||||
特殊的 V4L2_EVENT_ALL 类型,可用于退订所有事件。驱动可能在特殊
|
||||
情况下需要做此操作。
|
||||
|
||||
int v4l2_event_pending(struct v4l2_fh *fh)
|
||||
|
||||
返回未决事件的数量。有助于实现轮询(poll)操作。
|
||||
|
||||
事件通过 poll 系统调用传递到用户空间。驱动可用
|
||||
v4l2_fh->wait (wait_queue_head_t 类型)作为参数调用 poll_wait()。
|
||||
|
||||
事件分为标准事件和私有事件。新的标准事件必须使用可用的最小事件类型
|
||||
编号。驱动必须从他们本类型的编号起始处分配事件。类型的编号起始为
|
||||
V4L2_EVENT_PRIVATE_START + n * 1000 ,其中 n 为可用最小编号。每个
|
||||
类型中的第一个事件类型编号是为以后的使用保留的,所以第一个可用事件
|
||||
类型编号是‘class base + 1’。
|
||||
|
||||
V4L2 事件机制的使用实例可以在 OMAP3 ISP 的驱动
|
||||
(drivers/media/video/omap3isp)中找到。
|
113
Documentation/zh_CN/volatile-considered-harmful.txt
Normal file
113
Documentation/zh_CN/volatile-considered-harmful.txt
Normal file
|
@ -0,0 +1,113 @@
|
|||
Chinese translated version of Documentation/volatile-considered-harmful.txt
|
||||
|
||||
If you have any comment or update to the content, please contact the
|
||||
original document maintainer directly. However, if you have a problem
|
||||
communicating in English you can also ask the Chinese maintainer for
|
||||
help. Contact the Chinese maintainer if this translation is outdated
|
||||
or if there is a problem with the translation.
|
||||
|
||||
Maintainer: Jonathan Corbet <corbet@lwn.net>
|
||||
Chinese maintainer: Bryan Wu <bryan.wu@analog.com>
|
||||
---------------------------------------------------------------------
|
||||
Documentation/volatile-considered-harmful.txt 的中文翻译
|
||||
|
||||
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
|
||||
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
|
||||
译存在问题,请联系中文版维护者。
|
||||
|
||||
英文版维护者: Jonathan Corbet <corbet@lwn.net>
|
||||
中文版维护者: 伍鹏 Bryan Wu <bryan.wu@analog.com>
|
||||
中文版翻译者: 伍鹏 Bryan Wu <bryan.wu@analog.com>
|
||||
中文版校译者: 张汉辉 Eugene Teo <eugeneteo@kernel.sg>
|
||||
杨瑞 Dave Young <hidave.darkstar@gmail.com>
|
||||
以下为正文
|
||||
---------------------------------------------------------------------
|
||||
|
||||
为什么不应该使用“volatile”类型
|
||||
------------------------------
|
||||
|
||||
C程序员通常认为volatile表示某个变量可以在当前执行的线程之外被改变;因此,在内核
|
||||
中用到共享数据结构时,常常会有C程序员喜欢使用volatile这类变量。换句话说,他们经
|
||||
常会把volatile类型看成某种简易的原子变量,当然它们不是。在内核中使用volatile几
|
||||
乎总是错误的;本文档将解释为什么这样。
|
||||
|
||||
理解volatile的关键是知道它的目的是用来消除优化,实际上很少有人真正需要这样的应
|
||||
用。在内核中,程序员必须防止意外的并发访问破坏共享的数据结构,这其实是一个完全
|
||||
不同的任务。用来防止意外并发访问的保护措施,可以更加高效的避免大多数优化相关的
|
||||
问题。
|
||||
|
||||
像volatile一样,内核提供了很多原语来保证并发访问时的数据安全(自旋锁, 互斥量,内
|
||||
存屏障等等),同样可以防止意外的优化。如果可以正确使用这些内核原语,那么就没有
|
||||
必要再使用volatile。如果仍然必须使用volatile,那么几乎可以肯定在代码的某处有一
|
||||
个bug。在正确设计的内核代码中,volatile能带来的仅仅是使事情变慢。
|
||||
|
||||
思考一下这段典型的内核代码:
|
||||
|
||||
spin_lock(&the_lock);
|
||||
do_something_on(&shared_data);
|
||||
do_something_else_with(&shared_data);
|
||||
spin_unlock(&the_lock);
|
||||
|
||||
如果所有的代码都遵循加锁规则,当持有the_lock的时候,不可能意外的改变shared_data的
|
||||
值。任何可能访问该数据的其他代码都会在这个锁上等待。自旋锁原语跟内存屏障一样—— 它
|
||||
们显式的用来书写成这样 —— 意味着数据访问不会跨越它们而被优化。所以本来编译器认为
|
||||
它知道在shared_data里面将有什么,但是因为spin_lock()调用跟内存屏障一样,会强制编
|
||||
译器忘记它所知道的一切。那么在访问这些数据时不会有优化的问题。
|
||||
|
||||
如果shared_data被声名为volatile,锁操作将仍然是必须的。就算我们知道没有其他人正在
|
||||
使用它,编译器也将被阻止优化对临界区内shared_data的访问。在锁有效的同时,
|
||||
shared_data不是volatile的。在处理共享数据的时候,适当的锁操作可以不再需要
|
||||
volatile —— 并且是有潜在危害的。
|
||||
|
||||
volatile的存储类型最初是为那些内存映射的I/O寄存器而定义。在内核里,寄存器访问也应
|
||||
该被锁保护,但是人们也不希望编译器“优化”临界区内的寄存器访问。内核里I/O的内存访问
|
||||
是通过访问函数完成的;不赞成通过指针对I/O内存的直接访问,并且不是在所有体系架构上
|
||||
都能工作。那些访问函数正是为了防止意外优化而写的,因此,再说一次,volatile类型不
|
||||
是必需的。
|
||||
|
||||
另一种引起用户可能使用volatile的情况是当处理器正忙着等待一个变量的值。正确执行一
|
||||
个忙等待的方法是:
|
||||
|
||||
while (my_variable != what_i_want)
|
||||
cpu_relax();
|
||||
|
||||
cpu_relax()调用会降低CPU的能量消耗或者让位于超线程双处理器;它也作为内存屏障一样出
|
||||
现,所以,再一次,volatile不是必需的。当然,忙等待一开始就是一种反常规的做法。
|
||||
|
||||
在内核中,一些稀少的情况下volatile仍然是有意义的:
|
||||
|
||||
- 在一些体系架构的系统上,允许直接的I/0内存访问,那么前面提到的访问函数可以使用
|
||||
volatile。基本上,每一个访问函数调用它自己都是一个小的临界区域并且保证了按照
|
||||
程序员期望的那样发生访问操作。
|
||||
|
||||
- 某些会改变内存的内联汇编代码虽然没有什么其他明显的附作用,但是有被GCC删除的可
|
||||
能性。在汇编声明中加上volatile关键字可以防止这种删除操作。
|
||||
|
||||
- Jiffies变量是一种特殊情况,虽然每次引用它的时候都可以有不同的值,但读jiffies
|
||||
变量时不需要任何特殊的加锁保护。所以jiffies变量可以使用volatile,但是不赞成
|
||||
其他跟jiffies相同类型变量使用volatile。Jiffies被认为是一种“愚蠢的遗留物"
|
||||
(Linus的话)因为解决这个问题比保持现状要麻烦的多。
|
||||
|
||||
- 由于某些I/0设备可能会修改连续一致的内存,所以有时,指向连续一致内存的数据结构
|
||||
的指针需要正确的使用volatile。网络适配器使用的环状缓存区正是这类情形的一个例
|
||||
子,其中适配器用改变指针来表示哪些描述符已经处理过了。
|
||||
|
||||
对于大多代码,上述几种可以使用volatile的情况都不适用。所以,使用volatile是一种
|
||||
bug并且需要对这样的代码额外仔细检查。那些试图使用volatile的开发人员需要退一步想想
|
||||
他们真正想实现的是什么。
|
||||
|
||||
非常欢迎删除volatile变量的补丁 - 只要证明这些补丁完整的考虑了并发问题。
|
||||
|
||||
注释
|
||||
----
|
||||
|
||||
[1] http://lwn.net/Articles/233481/
|
||||
[2] http://lwn.net/Articles/233482/
|
||||
|
||||
致谢
|
||||
----
|
||||
|
||||
最初由Randy Dunlap推动并作初步研究
|
||||
由Jonathan Corbet撰写
|
||||
参考Satyam Sharma,Johannes Stezenbach,Jesper Juhl,Heikki Orsila,
|
||||
H. Peter Anvin,Philipp Hahn和Stefan Richter的意见改善了本档。
|
Loading…
Add table
Add a link
Reference in a new issue