Merge pull request #14 from LCTT/master

Update My Repository
This commit is contained in:
joeren 2014-06-24 15:47:51 +08:00
commit c3f23b239b
10 changed files with 909 additions and 892 deletions

View File

@ -8,15 +8,15 @@ APP Grid:一个优秀的Ubuntu软件中心替代品
### App GridUbuntu软件中心替代品 ###
自从2011年的彻底改造后Ubuntu的旗舰应用商店的界面就没怎么变过。这并不是说它在此期间被完全忽略了12.04的开发周期中可以看到[在启动时间上的工作][1]已经做了一些
自从2011年的彻底改造后Ubuntu的旗舰应用商店的界面就没怎么变过。这并不是说它在此期间被完全忽略了在12.04的开发周期中可以看到已经做了一些[减少打开耗时的工作][1]
撇开那个不算,ol USC还是一如既往一篮子的潜力还没被开发。
撇开那个不算,Ubuntu软件中心还是一如既往还有许多潜在功能还没被开发。
App Grid的目标解决这些问题。从零开始,它要求更快的启动时间,更快的反应时间,而且“不感觉混乱,不让人失望”。
App Grid的目标解决这些问题。从零开始,它要求更快的启动时间,更快的反应时间,而且“不感觉混乱,不让人失望”。
在大部分这些方面App Grid取得了成功。它几乎可以立即打开而在界面上点击也确实反应迅速。“不感觉混乱”这一承诺或许有一点小小的争议。该应用有时候要你横向滚动而另外的时候又要你纵向滚动。也有人禁不住会想如果这个应用能把它的网格背景样式扔了可能看起来会显得更专业一些。
作为在Ubuntu上筛选应用程序的一个方式App Grid做出了极大的努力。它支持Ubuntu One上的订购、评级和评论作为Ubuntu默认应用商店的替代品它更好用。
作为在Ubuntu上筛选应用程序的一个方式App Grid做出了极大的努力。它支持Ubuntu One上的订购、评级和评论作为Ubuntu默认应用商店的替代品它更好用。
如果非要说点什么缺点的话,那就是它不是一个开源的应用程序,第一次运行时会显示以下免责声明:
@ -27,18 +27,19 @@ App Grid的目标时解决这些问题。从零开始它要求更快的启动
App Grid可运行在Ubuntu 12.04 LTS13.10以及14.04 LTS版本下。可以通过添加以下PPA软件源来安装
sudo add-apt-repository -y ppa:appgrid/stable
sudo apt-get update && sudo apt-get install app grid
sudo apt-get update && sudo apt-get install appgrid
或者,也可以[从项目网站][2]抓取一个.deb安装包来安装。
- [下载用于Ubuntu 14.04的App Grid安装包][3]
试试吧,试过后请到我们开的空间里来发表一下你的看法吧……
试试吧,试过后请发表一下你的看法吧……
--------------------------------------------------------------------------------
via: http://www.omgubuntu.co.uk/2014/05/appgrid-ubuntu-software-centre-alternative
译者:[GOLinux](https://github.com/GOLinux) 校对:[校对者ID](https://github.com/校对者ID)
译者:[GOLinux](https://github.com/GOLinux) 校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出

View File

@ -0,0 +1,370 @@
黑客内核编写属于你的第一个Linux内核模块
================================================================================
> 曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引你走向前方……
内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中它八成就是了。Linux内核和它的用户空间是大不相同的抛开漫不经心你必须小心翼翼因为你编程中的一个bug就会影响到整个系统。浮点运算做起来可不容易堆栈固定而狭小而你写的代码总是异步的因此你需要想想并发会导致什么。而除了所有这一切之外Linux内核只是一个很大的、很复杂的C程序它对每个人开放任何人都去读它、学习它并改进它而你也可以是其中之一。
学习内核编程的最简单的方式也许就是写个内核模块一段可以动态加载进内核的代码。模块所能做的事是有限的——例如他们不能在类似进程描述符这样的公共数据结构中增减字段LCTT译注可能会破坏整个内核及系统的功能。但是在其它方面他们是成熟的内核级的代码可以在需要时随时编译进内核这样就可以摒弃所有的限制了。完全可以在Linux源代码树以外来开发并编译一个模块这并不奇怪它称为树外开发如果你只是想稍微玩玩而并不想提交修改以包含到主线内核中去这样的方式是很方便的。
在本教程中,我们将开发一个简单的内核模块用以创建一个**/dev/reverse**设备。写入该设备的字符串将以相反字序的方式读回“Hello World”读成“World Hello”。这是一个很受欢迎的程序员面试难题当你利用自己的能力在内核级别实现这个功能时可以使你得到一些加分。在开始前有一句忠告你的模块中的一个bug就会导致系统崩溃虽然可能性不大但还是有可能的和数据丢失。在开始前请确保你已经将重要数据备份或者采用一种更好的方式在虚拟机中进行试验。
### 尽可能不要用root身份 ###
> 默认情况下,**/dev/reverse**只有root可以使用因此你只能使用**sudo**来运行你的测试程序。要解决该限制,可以创建一个包含以下内容的**/lib/udev/rules.d/99-reverse.rules**文件:
>
> SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"
>
> 别忘了重新插入模块。让非root用户访问设备节点往往不是一个好主意但是在开发其间却是十分有用的。这并不是说以root身份运行二进制测试文件也不是个好主意。
#### 模块的构造 ####
由于大多数的Linux内核模块是用C写的除了底层的特定于体系结构的部分所以推荐你将你的模块以单一文件形式保存例如reverse.c。我们已经把完整的源代码放在GitHub上——这里我们将看其中的一些片段。开始时我们先要包含一些常见的文件头并用预定义的宏来描述模块
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Valentine Sinitsyn <valentine.sinitsyn@gmail.com>");
MODULE_DESCRIPTION("In-kernel phrase reverser");
这里一切都直接明了,除了**MODULE\_LICENSE()**它不仅仅是一个标记。内核坚定地支持GPL兼容代码因此如果你把许可证设置为其它非GPL兼容的“Proprietary”[专利]),某些特定的内核功能将在你的模块中不可用。
### 什么时候不该写内核模块 ###
> 内核编程很有趣,但是在现实项目中写(尤其是调试)内核代码要求特定的技巧。通常来讲,在没有其它方式可以解决你的问题时,你才应该在内核级别解决它。以下情形中,可能你在用户空间中解决它更好:
> - 你要开发一个USB驱动 —— 请查看[libusb][1]。
> - 你要开发一个文件系统 —— 试试[FUSE][2]。
> - 你在扩展Netfilter —— 那么[libnetfilter\_queue][3]对你有所帮助。
> 通常,内核里面代码的性能会更好,但是对于许多项目而言,这点性能丢失并不严重。
由于内核编程总是异步的,没有一个**main()**函数来让Linux顺序执行你的模块。取而代之的是你要为各种事件提供回调函数像这个
static int __init reverse_init(void)
{
printk(KERN_INFO "reverse device has been registered\n");
return 0;
}
static void __exit reverse_exit(void)
{
printk(KERN_INFO "reverse device has been unregistered\n");
}
module_init(reverse_init);
module_exit(reverse_exit);
这里,我们定义的函数被称为模块的插入和删除。只有第一个的插入函数是必要的。目前,它们只是打印消息到内核环缓冲区(可以在用户空间通过**dmesg**命令访问);**KERN\_INFO**是日志级别(注意,没有逗号)。**\_\_init**和**\_\_exit**是属性 —— 联结到函数或者变量的元数据片。属性在用户空间的C代码中是很罕见的但是内核中却很普遍。所有标记为**\_\_init**的会在初始化后释放内存以供重用还记得那条过去内核的那条“Freeing unused kernel memory…[释放未使用的内核内存……]”信息吗?)。**\_\_exit**表明,当代码被静态构建进内核时,该函数可以安全地优化了,不需要清理收尾。最后,**module\_init()**和**module\_exit()**这两个宏将**reverse\_init()**和**reverse_exit()**函数设置成为我们模块的生命周期回调函数。实际的函数名称并不重要,你可以称它们为**init()**和**exit()**,或者**start()**和**stop()**,你想叫什么就叫什么吧。他们都是静态声明,你在外部模块是看不到的。事实上,内核中的任何函数都是不可见的,除非明确地被导出。然而,在内核程序员中,给你的函数加上模块名前缀是约定俗成的。
这些都是些基本概念 - 让我们来做更多有趣的事情吧。模块可以接收参数,就像这样:
# modprobe foo bar=1
**modinfo**命令显示了模块接受的所有参数,而这些也可以在**/sys/module//parameters**下作为文件使用。我们的模块需要一个缓冲区来存储参数 —— 让我们把这大小设置为用户可配置。在**MODULE_DESCRIPTION()**下添加如下三行:
static unsigned long buffer_size = 8192;
module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));
MODULE_PARM_DESC(buffer_size, "Internal buffer size");
这儿我们定义了一个变量来存储该值封装成一个参数并通过sysfs来让所有人可读。这个参数的描述最后一行出现在modinfo的输出中。
由于用户可以直接设置**buffer\_size**,我们需要在**reverse\_init()**来清除无效取值。你总该检查来自内核之外的数据 —— 如果你不这么做,你就是将自己置身于内核异常或安全漏洞之中。
static int __init reverse_init()
{
if (!buffer_size)
return -1;
printk(KERN_INFO
"reverse device has been registered, buffer size is %lu bytes\n",
buffer_size);
return 0;
}
来自模块初始化函数的非0返回值意味着模块执行失败。
### 导航 ###
> 但你开发模块时Linux内核就是你所需一切的源头。然而它相当大你可能在查找你所要的内容时会有困难。幸运的是在庞大的代码库面前有许多工具使这个过程变得简单。首先是Cscope —— 在终端中运行的一个比较经典的工具。你所要做的,就是在内核源代码的顶级目录中运行**make cscope && cscope**。Cscope和Vim以及Emacs整合得很好因此你可以在你最喜爱的编辑器中使用它。
> 如果基于终端的工具不是你的最爱,那么就访问[http://lxr.free-electrons.com][4]吧。它是一个基于web的内核导航工具即使它的功能没有Cscope来得多例如你不能方便地找到函数的用法但它仍然提供了足够多的快速查询功能。
现在是时候来编译模块了。你需要你正在运行的内核版本头文件(**linux-headers**,或者等同的软件包)和**build-essential**或者类似的包。接下来该创建一个标准的Makefile模板
obj-m += reverse.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
现在,调用**make**来构建你的第一个模块。如果你输入的都正确在当前目录内会找到reverse.ko文件。使用**sudo insmod reverse.ko**插入内核模块,然后运行如下命令:
$ dmesg | tail -1
[ 5905.042081] reverse device has been registered, buffer size is 8192 bytes
恭喜了!然而,目前这一行还只是假象而已 —— 还没有设备节点呢。让我们来搞定它。
#### 混杂设备 ####
在Linux中有一种特殊的字符设备类型叫做“混杂设备”或者简称为“misc”。它是专为单一接入点的小型设备驱动而设计的而这正是我们所需要的。所有混杂设备共享同一个主设备号10因此一个驱动(**drivers/char/misc.c**)就可以查看它们所有设备了,而这些设备用次设备号来区分。从其他意义来说,它们只是普通字符设备。
要为该设备注册一个次设备号(以及一个接入点),你需要声明**struct misc\_device**,填上所有字段(注意语法),然后使用指向该结构的指针作为参数来调用**misc\_register()**。为此,你也需要包含**linux/miscdevice.h**头文件:
static struct miscdevice reverse_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "reverse",
.fops = &reverse_fops
};
static int __init reverse_init()
{
...
misc_register(&reverse_misc_device);
printk(KERN_INFO ...
}
这儿我们为名为“reverse”的设备请求一个第一个可用的动态的次设备号省略号表明我们之前已经见过的省略的代码。别忘了在模块卸下后注销掉该设备。
static void __exit reverse_exit(void)
{
misc_deregister(&reverse_misc_device);
...
}
fops字段存储了一个指针指向一个**file\_operations**结构在Linux/fs.h中声明而这正是我们模块的接入点。**reverse\_fops**定义如下:
static struct file_operations reverse_fops = {
.owner = THIS_MODULE,
.open = reverse_open,
...
.llseek = noop_llseek
};
另外,**reverse\_fops**包含了一系列回调函数(也称之为方法),当用户空间代码打开一个设备,读写或者关闭文件描述符时,就会执行。如果你要忽略这些回调,可以指定一个明确的回调函数来替代。这就是为什么我们将**llseek**设置为**noop\_llseek()**,(顾名思义)它什么都不干。这个默认实现改变了一个文件指针,而且我们现在并不需要我们的设备可以寻址(这是今天留给你们的家庭作业)。
#### 关闭和打开 ####
让我们来实现该方法。我们将给每个打开的文件描述符分配一个新的缓冲区并在它关闭时释放。这实际上并不安全如果一个用户空间应用程序泄漏了描述符也许是故意的它就会霸占RAM并导致系统不可用。在现实世界中你总得考虑到这些可能性。但在本教程中这种方法不要紧。
我们需要一个结构函数来描述缓冲区。内核提供了许多常规的数据结构链接列表双联的哈希表树等等之类。不过缓冲区常常从头设计。我们将调用我们的“struct buffer”
struct buffer {
char *data, *end, *read_ptr;
unsigned long size;
};
**data**是该缓冲区存储的一个指向字符串的指针,而**end**指向字符串结尾后的第一个字节。**read_ptr**是**read()**开始读取数据的地方。缓冲区的size是为了保证完整性而存储的 —— 目前,我们还没有使用该区域。你不能假设使用你结构体的用户会正确地初始化所有这些东西,所以最好在函数中封装缓冲区的分配和收回。它们通常命名为**buffer\_alloc()**和**buffer\_free()**。
static struct buffer *buffer_alloc(unsigned long size)
{
struct buffer *buf;
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (unlikely(!buf))
goto out;
...
out:
return buf;
}
内核内存使用**kmalloc()**来分配,并使用**kfree()**来释放;**kzalloc()**的风格是将内存设置为全零。不同于标准的**malloc()**,它的内核对应部分收到的标志指定了第二个参数中请求的内存类型。这里,**GFP_KERNEL**是说我们需要一个普通的内核内存不是在DMA或高内存区中以及如果需要的话函数可以睡眠重新调度进程。**sizeof(*buf)**是一种常见的方式,它用来获取可通过指针访问的结构体的大小。
你应该随时检查**kmalloc()**的返回值访问NULL指针将导致内核异常。同时也需要注意**unlikely()**宏的使用。它(及其相对宏**likely()**)被广泛用于内核中,用于表明条件几乎总是真的(或假的)。它不会影响到控制流程,但是能帮助现代处理器通过分支预测技术来提升性能。
最后,注意**goto**语句。它们常常为认为是邪恶的但是Linux内核以及一些其它系统软件采用它们来实施集中式的函数退出。这样的结果是减少嵌套深度使代码更具可读性而且非常像更高级语言中的**try-catch**区块。
有了**buffer\_alloc()**和**buffer\_free()****open**和**close**方法就变得很简单了。
static int reverse_open(struct inode *inode, struct file *file)
{
int err = 0;
file->private_data = buffer_alloc(buffer_size);
...
return err;
}
**struct file**是一个标准的内核数据结构,用以存储打开的文件的信息,如当前文件位置(**file->f\_pos**)、标志(**file->f\_flags**),或者打开模式(**file->f\_mode**)等。另外一个字段**file->privatedata**用于关联文件到一些专有数据它的类型是void *,而且它在文件拥有者以外,对内核不透明。我们将一个缓冲区存储在那里。
如果缓冲区分配失败,我们通过返回否定值(**-ENOMEM**来为调用的用户空间代码标明。一个C库中调用的**open(2)**系统调用(如 **glibc**)将会检测这个并适当地设置**errno** 。
#### 学习如何读和写 ####
“read”和“write”方法是真正完成工作的地方。当数据写入到缓冲区时我们放弃之前的内容和反向地存储该字段不需要任何临时存储。**read**方法仅仅是从内核缓冲区复制数据到用户空间。但是如果缓冲区还没有数据,**revers\_eread()**会做什么呢?在用户空间中,**read()**调用会在有可用数据前阻塞它。在内核中你就必须等待。幸运的是有一项机制用于处理这种情况就是wait queues
想法很简单。如果当前进程需要等待某个事件,它的描述符(**struct task_struct**存储current信息被放进非可运行睡眠中状态并添加到一个队列中。然后**schedule()**就被调用来选择另一个进程运行。生成事件的代码通过使用队列将等待进程放回**TASK\_RUNNING**状态来唤醒它们。调度程序将在以后在某个地方选择它们之一。Linux有多种非可运行状态最值得注意的是**TASK\_INTERRUPTIBLE**(一个可以通过信号中断的睡眠)和**TASK\_KILLABLE**(一个可被杀死的睡眠中的进程)。所有这些都应该正确处理,并等待队列为你做这些事。
一个用以存储读取等待队列头的天然场所就是结构缓冲区,所以从为它添加**wait\_queue\_head_t read\_queue**字段开始。你也应该包含**linux/sched.h**头文件。可以使用DECLARE\_WAITQUEUE()宏来静态声明一个等待队列。在我们的情况下,需要动态初始化,因此添加下面这行到**buffer\_alloc()**
init_waitqueue_head(&buf->read_queue);
我们等待可用数据;或者等待**read\_ptr != end**条件成立。我们也想要让等待操作可以被中断通过Ctrl+C。因此“read”方法应该像这样开始
static ssize_t reverse_read(struct file *file, char __user * out,
size_t size, loff_t * off)
{
struct buffer *buf = file->private_data;
ssize_t result;
while (buf->read_ptr == buf->end) {
if (file->f_flags & O_NONBLOCK) {
result = -EAGAIN;
goto out;
}
if (wait_event_interruptible
(buf->read_queue, buf->read_ptr != buf->end)) {
result = -ERESTARTSYS;
goto out;
}
}
...
我们让它循环,直到有可用数据,如果没有则使用**wait\_event\_interruptible()**(它是一个宏,不是函数,这就是为什么要通过值的方式给队列传递)来等待。好吧,如果**wait\_event\_interruptible()**被中断它返回一个非0值这个值代表**-ERESTARTSYS**。这段代码意味着系统调用应该重新启动。**file->f\_flags**检查以非阻塞模式打开的文件数:如果没有数据,返回**-EAGAIN**。
我们不能使用**if()**来替代**while()**,因为可能有许多进程正等待数据。当**write**方法唤醒它们时,调度程序以不可预知的方式选择一个来运行,因此,在这段代码有机会执行的时候,缓冲区可能再次空出。现在,我们需要将数据从**buf->data** 复制到用户空间。**copy\_to\_user()**内核函数就干了此事:
size = min(size, (size_t) (buf->end - buf->read_ptr));
if (copy_to_user(out, buf->read_ptr, size)) {
result = -EFAULT;
goto out;
}
如果用户空间指针错误,那么调用可能会失败;如果发生了此事,我们就返回**-EFAULT**。记住,不要相信任何来自内核外的事物!
buf->read_ptr += size;
result = size;
out:
return result;
}
为了使数据在任意块可读,需要进行简单运算。该方法返回读入的字节数,或者一个错误代码。
写方法更简短。首先,我们检查缓冲区是否有足够的空间,然后我们使用**copy\_from\_userspace()**函数来获取数据。再然后**read\_ptr**和结束指针会被重置,并且反转存储缓冲区内容:
buf->end = buf->data + size;
buf->read_ptr = buf->data;
if (buf->end > buf->data)
reverse_phrase(buf->data, buf->end - 1);
这里, **reverse\_phrase()**干了所有吃力的工作。它依赖于**reverse\_word()**函数,该函数相当简短并且标记为内联。这是另外一个常见的优化;但是,你不能过度使用。因为过多的内联会导致内核映像徒然增大。
最后,我们需要唤醒**read\_queue**中等待数据的进程,就跟先前讲过的那样。**wake\_up\_interruptible()**就是用来干此事的:
wake_up_interruptible(&buf->read_queue);
耶!你现在已经有了一个内核模块,它至少已经编译成功了。现在,是时候来测试了。
### 调试内核代码 ###
> 或许,内核中最常见的调试方法就是打印。如果你愿意,你可以使用普通的**printk()** (假定使用**KERN\_DEBUG**日志等级。然而那儿还有更好的办法。如果你正在写一个设备驱动这个设备驱动有它自己的“struct device”可以使用**pr\_debug()**或者**dev\_dbg()**:它们支持动态调试(**dyndbg**)特性,并可以根据需要启用或者禁用(请查阅**Documentation/dynamic-debug-howto.txt**)。对于单纯的开发消息,使用**pr\_devel()**除非设置了DEBUG否则什么都不会做。要为我们的模块启用DEBUG请添加以下行到Makefile中
> CFLAGS_reverse.o := -DDEBUG
>
> 完了之后,使用**dmesg**来查看**pr_debug()**或**pr_devel()**生成的调试信息。
> 或者,你可以直接发送调试信息到控制台。要想这么干,你可以设置**console_loglevel**内核变量为8或者更大的值**echo 8 /proc/sys/kernel/printk**),或者在高日志等级,如**KERN_ERR**,来临时打印要查询的调试信息。很自然,在发布代码前,你应该移除这样的调试声明。
> 注意内核消息出现在控制台不要在Xterm这样的终端模拟器窗口中去查看这也是在内核开发时建议你不在X环境下进行的原因。
### 惊喜,惊喜! ###
编译模块,然后加载进内核:
$ make
$ sudo insmod reverse.ko buffer_size=2048
$ lsmod
reverse 2419 0
$ ls -l /dev/reverse
crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse
一切似乎就位。现在,要测试模块是否正常工作,我们将写一段小程序来翻转它的第一个命令行参数。**main()**(再三检查错误)可能看上去像这样:
int fd = open("/dev/reverse", O_RDWR);
write(fd, argv[1], strlen(argv[1]));
read(fd, argv[1], strlen(argv[1]));
printf("Read: %s\n", argv[1]);
像这样运行:
$ ./test 'A quick brown fox jumped over the lazy dog'
Read: dog lazy the over jumped fox brown quick A
它工作正常!玩得更逗一点:试试传递单个单词或者单个字母的短语,空的字符串或者是非英语字符串(如果你有这样的键盘布局设置),以及其它任何东西。
现在,让我们让事情变得更好玩一点。我们将创建两个进程,它们共享一个文件描述符(及其内核缓冲区)。其中一个会持续写入字符串到设备,而另一个将读取这些字符串。在下例中,我们使用了**fork(2)**系统调用而pthreads也很好用。我也省略打开和关闭设备的代码并在此检查代码错误又来了
char *phrase = "A quick brown fox jumped over the lazy dog";
if (fork())
/* Parent is the writer */
while (1)
write(fd, phrase, len);
else
/* child is the reader */
while (1) {
read(fd, buf, len);
printf("Read: %s\n", buf);
}
你希望这个程序会输出什么呢?下面就是在我的笔记本上得到的东西:
Read: dog lazy the over jumped fox brown quick A
Read: A kcicq brown fox jumped over the lazy dog
Read: A kciuq nworb xor jumped fox brown quick A
Read: A kciuq nworb xor jumped fox brown quick A
...
这里发生了什么呢?就像举行了一场比赛。我们认为**read**和**write**是原子操作,或者从头到尾一次执行一个指令。然而,内核确实无序并发的,随便就重新调度了**reverse\_phrase()**函数内部某个地方运行着的写入操作的内核部分。如果在写入操作结束前就调度了**read()**操作呢就会产生数据不完整的状态。这样的bug非常难以找到。但是怎样来处理这个问题呢
基本上,我们需要确保在写方法返回前没有**read**方法能被执行。如果你曾经编写过一个多线程的应用程序你可能见过同步原语如互斥锁或者信号。Linux也有这些但有些细微的差别。内核代码可以运行进程上下文用户空间代码的“代表”工作就像我们使用的方法和终端上下文例如一个IRQ处理线程。如果你已经在进程上下文中和并且你已经得到了所需的锁你只需要简单地睡眠和重试直到成功为止。在中断上下文时你不能处于休眠状态因此代码会在一个循环中运行直到锁可用。关联原语被称为自旋锁但在我们的环境中一个简单的互斥锁 —— 在特定时间内只有唯一一个进程能“占有”的对象 —— 就足够了。处于性能方面的考虑,现实的代码可能也会使用读-写信号。
锁总是保护某些数据在我们的环境中是一个“struct buffer”实例而且也常常会把它们嵌入到它们所保护的结构体中。因此我们添加一个互斥锁struct mutex lock到“struct buffer”中。我们也必须用**mutex\_init()**来初始化互斥锁;**buffer\_alloc**是用来处理这件事的好地方。使用互斥锁的代码也必须包含**linux/mutex.h**。
互斥锁很像交通信号灯 —— 要是司机不看它和不听它的,它就没什么用。因此,在对缓冲区做操作并在操作完成时释放它之前,我们需要更新**reverse\_read()**和**reverse\_write()**来获取互斥锁。让我们来看看**read**方法 —— **write**的工作原理相同:
static ssize_t reverse_read(struct file *file, char __user * out,
size_t size, loff_t * off)
{
struct buffer *buf = file->private_data;
ssize_t result;
if (mutex_lock_interruptible(&buf->lock)) {
result = -ERESTARTSYS;
goto out;
}
我们在函数一开始就获取锁。**mutex\_lock\_interruptible()**要么得到互斥锁然后返回,要么让进程睡眠,直到有可用的互斥锁。就像前面一样,**\_interruptible**后缀意味着睡眠可以由信号来中断。
while (buf->read_ptr == buf->end) {
mutex_unlock(&buf->lock);
/* ... wait_event_interruptible() here ... */
if (mutex_lock_interruptible(&buf->lock)) {
result = -ERESTARTSYS;
goto out;
}
}
下面是我们的“等待数据”循环。当获取互斥锁时,或者发生称之为“死锁”的情境时,不应该让进程睡眠。因此,如果没有数据,我们释放互斥锁并调用**wait\_event\_interruptible()**。当它返回时,我们重新获取互斥锁并像往常一样继续:
if (copy_to_user(out, buf->read_ptr, size)) {
result = -EFAULT;
goto out_unlock;
}
...
out_unlock:
mutex_unlock(&buf->lock);
out:
return result;
最后,当函数结束,或者在互斥锁被获取过程中发生错误时,互斥锁被解锁。重新编译模块(别忘了重新加载),然后再次进行测试。现在你应该没发现毁坏的数据了。
### 接下来是什么? ###
现在你已经尝试了一次内核黑客。我们刚刚为你揭开了这个话题的外衣里面还有更多东西供你探索。我们的第一个模块有意识地写得简单一点在从中学到的概念在更复杂的环境中也一样。并发、方法表、注册回调函数、使进程睡眠以及唤醒进程这些都是内核黑客们耳熟能详的东西而现在你已经看过了它们的运作。或许某天你的内核代码也将被加入到主线Linux源代码树中 —— 如果真这样,请联系我们!
--------------------------------------------------------------------------------
via: http://www.linuxvoice.com/be-a-kernel-hacker/
译者:[GOLinux](https://github.com/GOLinux) [disylee](https://github.com/disylee) 校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://www.libusb.org/
[2]:http://fuse.sf.net/
[3]:http://www.linuxvoice.com/be-a-kernel-hacker/www.netfilter.org/projects/libnetfilter_queue
[4]:http://lxr.free-electrons.com/

View File

@ -2,11 +2,12 @@ Linux使用bash删除目录中的特定文件
================================================================================
![](http://s0.cyberciti.org/images/category/old/terminal.png)
我是一个Linux新手用户。现在我需要清理一个下载目录中的文件其实我就是想删除/Download/文件夹下面除了以下格式的文件外所以其它文件:
*.iso - 所有的iso格式的文件。
*.zip - 所有zip格式的文件。
我是一名Linux新用户。现在我需要清理一个下载目录中的文件其实我就是想从/Download/文件夹删去除了以下格式的文件外所以其它文件:
我如何在一个基于LinuxOS X 或者Unix-like系统上的bash shell中删除特定的文件呢
- *.iso - 所有的iso格式的文件。
- *.zip - 所有zip格式的文件。
我如何在一个基于LinuxOS X 或者 Unix-like 系统上的bash shell中删除特定的文件呢
Bash shell 支持丰富的文件模式匹配符例如:
@ -18,13 +19,15 @@ Bash shell 支持丰富的文件模式匹配符例如:
这里你需要用系统内置的shopt命令来开启shell中的extglob选项然后你就可以使用扩展的模式符了这些模式匹配符如下
1. ?(pattern-list) 匹配零次或一次给定的模式。
1. *(pattern-list) -至少匹配零次给定的模式。
1. +(pattern-list) 至少匹配一次给定的模式。
1. @(pattern-list) 匹配一次给定的模式。
1. !(pattern-list) 匹配所有除给定模式以外的模式。
1. ?(模式列表) 匹配零次或一次给定的模式。
1. *(模式列表) 匹配零次或多次给定的模式。
1. +(模式列表) 至少匹配一次给定的模式。
1. @(模式列表) 匹配一次给定的模式。
1. !(模式列表) 不匹配给定模式。
一个模式列表就是一个或多个用 | 分开的模式文件名。首先打开extgolb选项
一个模式列表就是一个或多个用 | 分开的模式(文件名)。
首先要打开extgolb选项
shopt -s extglob
@ -47,20 +50,21 @@ rm 命令的语法格式为:
## 你也可以使用完整的目录 ##
rm /Users/vivek/!(*.zip|*.iso|*.mp3)
## 传递参数 ##
rm [options] !(*.zip|*.iso)
## 也可以传递参数 ##
rm [选项] !(*.zip|*.iso)
rm -v !(*.zip|*.iso)
rm -f !(*.zip|*.iso)
rm -v -i !(*.php)
最后,关闭 extglob 选项:
最后,关闭 extglob 选项方法如下
shopt -u extglob
### 策略 #2: 使用bash的 GLOBIGNORE 变量删除指定文件以外的所有文件 ###
摘自 [bash(1)][1] 手册页:
> 一个用冒号分开的模式列表定义了被路径扩展忽略的文件的集合。如果一个文件同时与路径扩展模式和GLOBIGNORE中的模式匹配那么它就从匹配列表中移除了。
> 这是一个用冒号分开的模式列表通过路径展开方式定义了要忽略的文件集合。如果一个匹配到路径展开模式的文件也匹配GLOBIGNORE中的模式那么它会从匹配列表中移除。
要删除所有文件只保留 zip 和 iso 文件,应如下设置 GLOBIGNORE
@ -75,16 +79,16 @@ rm 命令的语法格式为:
如果你正在使用 tcsh/csh/sh/ksh 或者其它shell你可以在Unixlike系统上试着用下面find命令的语法格式来删除文件
find /dir/ -type f -not -name 'PATTERN' -delete
find /dir/ -type f -not -name '匹配模式' -delete
或者
## 对于怪异的文件名可以使用 xargs ##
find /dir/ -type f -not -name 'PATTERN' -print0 | xargs -0 -I {} rm {}
find /dir/ -type f -not -name 'PATTERN' -print0 | xargs -0 -I {} rm [options] {}
find /dir/ -type f -not -name '匹配模式' -print0 | xargs -0 -I {} rm {}
find /dir/ -type f -not -name '匹配模式' -print0 | xargs -0 -I {} rm [选项] {}
为了删除 /source 目录下除 php 以外的文件,键入:
想要删除 /source 目录下除 php 以外的文件,键入:
find ~/sources/ -type f -not -name '*.php' -delete
@ -103,9 +107,9 @@ rm 命令的语法格式为:
via: http://www.cyberciti.biz/faq/linux-bash-delete-all-files-in-directory-except-few/
译者:[Linchenguang](https://github.com/Linchenguang) 校对:[校对者ID](https://github.com/校对者ID)
译者:[Linchenguang](https://github.com/Linchenguang) 校对:[Caroline](https://github.com/carolinewuyan)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://www.manpager.com/linux/man1/bash.1.html
[2]:http://www.manpager.com/linux/man1/find.1.html
[2]:http://www.manpager.com/linux/man1/find.1.html

View File

@ -0,0 +1,190 @@
9 ASCII Games You'll Want to Play Again and Again
================================================================================
Modern Graphics Processing Units (GPUs) offer exceptional gaming capabilities, and have contributed to the trend of astonishing leaps in graphics fidelity. There is not a year that has gone by without a game being released that makes significant advances in technical graphics wizardry. Computer graphics have been advancing at a staggering pace. At the current rate of progress, in the next 10 years it may not be possible to distinguish computer graphics from reality.
Personally, these developments do not overly interest me. I find little fascination playing games that focus so much on the visuals they neglect the essential elements. Too often the storyline and game play has been compromised for visual quality. Most of my favourite games are somewhat deficient in the graphics department. Gameplay is always king in my eyes.
Linux has an excellent library of free games many of which are released under an open source license. The vast majority of these games are aesthetically pleasing. Popular games often have full motion video, vector graphics, 3D graphics, realistic 3D rendering, animation, texturing, a physics engine, and much more. Early computer games did not have these graphic techniques. The earliest video games were text games or text-based games that used text characters rather than vector or bitmapped graphics.
Text-based games often receive little coverage in the Linux press. However, there are some real ASCII gems out there waiting to be explored which are immensely addictive and great fun to play.
The idiom 'don't judge a book by its cover' can be extended to 'don't judge a computer game by its graphics'. Whilst the games featured in this article have extremely basic graphics, they have many redeeming qualities beyond evoking fond memories of the early days of computer gaming.
There are no fancy graphics here, just great gameplay coupled with the urge of always having just one more play.
![](http://www.linuxlinks.com/portal/content2/png/UnNetHack.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-UnNetHack.png)
The first game in this roundup is UnNetHack, a fork of NetHack, originally based on the hugely popular roguelike game NetHack. NetHack was first released in 1987, and is considered by many gamers to be one of the best gaming experiences the computing world offers.
UnNetHack adds a number of enhancements to NetHack, such as additional monsters, more levels, a few new objects, additional dangers, more challenging gameplay, and most importantly more entertainment than vanilla NetHack. It offers a tutorial to help new players get started.
Be warned, UnNetHack is fiendishly addictive.
- Website: [sourceforge.net/apps/trac/unnethack][1]
- Authors: Patric Mueller
- License: Nethack General Public License
- Version Number: 5.1.0
----------
![](http://www.linuxlinks.com/portal/content2/png/VMSEmpire.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-vms-empire.png)
Empire is a simulation of a full-scale war between two emperors, the computer and you. Naturally, there is only room for one, so the object of the game is to destroy the other. The computer plays by the same rules that you do.
This game is the ancestor of all the multiplayer 4X simulations out there, including Civilization and Master of Orion. The classic game from the 1980s uses text mode graphical output, drawing your units, cities and the world in color. Commands are issued using the keyboard.
The world on which the game takes place is a square rectangle containing cities, land, and water. Cities are used to build armies, planes, and ships which can move across the world destroying enemy pieces, exploring, and capturing more cities. The objective of the game is to destroy all the enemy pieces, and capture all the cities.
The game starts by assigning you one city and the computer one city. Cities can produce new pieces. Every city that you own produces more pieces for you according to the cost of the desired piece. The typical play of the game is to issue the Automove command until you decide to do something special. During movement in each round, the player is prompted to move each piece that does not otherwise have an assigned function.
- Website: [www.catb.org/~esr/vms-empire][2]
- Authors: Chuck Simmons, Eric S. Raymond
- License: GNU GPL v2
- Version Number: 1.12
----------
![](http://www.linuxlinks.com/portal/content2/png/Intricacy.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-Intricacy.png)
Intricacy is an addictive, open source, networked, video puzzle game. It is written in Haskell, using the Curses and SDL libraries.
Intricacy runs directly from the command-line, and provides a turn-based, abstract puzzle game where the players need to pick locks, simply by coordinating a couple of tools in order to manipulate the locks mechanism. Constructing and solving difficult puzzles within certain strict design constraints is both challenging and good fun.
The catch is that you will be able to pick locks that are designed by other players. It has multi-platform support, with binaries for both Linux and Windows.
- Website: [mbays.freeshell.org/intricacy][3]
- Authors: Chuck Simmons, Eric S. Raymond
- License: GNU GPL v3
- Version Number: 0.2.6.3
----------
![](http://www.linuxlinks.com/portal/content2/png/XorCurses.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-XorCurses.png)
XorCurses is a puzzle game set inside a series of mazes. It is a remake of XOR by Astral Software, a game published in 1987 and released on the popular home computers of the day including the Commodore 64, ZX Spectrum, Atari ST, and Amiga. XOR is a pure puzzle game with no random or arcade elements.
In some respects, XorCurses is a regression from the graphics of the old 8 bit computers as it uses even more simplistic graphics, with coloured ASCII characters instead of pixel based graphics.
XorCurses attempts to faithfully recreate that game for Linux, with particular attention placed on the behaviour of the objects within the original game.
The basic premise of Xor is to roam around a series of mazes collecting all of the blue masks and then finding the exit. You have two player-shields to aid you and you can use either one at any time and switch between them. The first few levels are easy to progress, but the rest are progressively harder to solve. A particularly challenging and difficult puzzle game that will keep you engaged for hours.
- Website: [www.jwm-art.net/dark.php?p=XorCurses][4]
- Authors: James W. Morris
- License: GNU GPL
- Version Number: 0.2.2
----------
![](http://www.linuxlinks.com/portal/content2/png/GoblinHack.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-GoblinHack.png)
Goblin Hack is an open source roguelike OpenGL-based smooth-scrolling ASCII graphics game. The game is inspired by the likes of NetHack, but faster with fewer keys.
Goblin Hack has a simple interface that appears to appeal to players of all ages, and fires their imagination in today's world of over-rendered games.
Players can choose one of several classes before being thrown into the first floor of a randomized, ongoing dungeon.
- Website: [goblinhack.sourceforge.net][5]
- Authors: Neil McGill
- License: GNU GPL v2
- Version Number: 1.18
----------
![](http://www.linuxlinks.com/portal/content2/png/CurseofWar.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-CurseofWar.png)
Curse of War is a fast-paced real time strategy game released under an open source license. It is implemented using C and ncurses. There is also an SDL version available.
The core game mechanics turns out to be quite close to WWI-WWII type of warfare, however, there is no explicit reference to any historical period.
Unlike most real time strategy games, in Curse of War players do not control units, but instead they concentrate on high-level strategic planning: Building infrastructure, securing resources, and moving armies.
A multiplayer mode is available. Computer opponents differ in personality, and it affects the way they fight.
- Website: [a-nikolaev.github.io/curseofwar][6]
- Authors: Alexey Nikolaev
- License: GNU GPL v3
- Version Number: 1.2.0
----------
![](http://www.linuxlinks.com/portal/content2/png/Brogue.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-Brogue.png)
Brogue is an open source Roguelike game for Mac OS X, Windows, Linux, iOS and Android.
Brogue is a direct descendant of Rogue, a dungeon crawling video game first developed by Michael Toy and Glenn Wichman around 1980. Unlike other popular modern roguelikes, Brogue favors simplicity over complexity, while trying to ensure that the interactions between components are interesting and varied.
Your goal is to travel to the 26th subterranean floor of the dungeon, retrieve the Amulet of Yendor and return with it to the surface. For the truly skillful who desire further challenge, depths below 26 contain three lumenstones each, items which confer an increased score upon victory.
Brogue is a challenging game, but still great fun to play. Try not to be disheartened by the difficulty of the game; with some application, Brogue will become very addictive.
- Website: [sites.google.com/site/broguegame][7]
- Authors: Brian Walker
- License: GNU Affero GPL
- Version Number: 1.7.3
----------
![](http://www.linuxlinks.com/portal/content2/png/DiabloRL.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-DiabloRL.png)
DiabloRL is a roguelike "unmake" of the popular Blizzard game Diablo 1 classic RPG to a turn-based ASCII roguelike.
The game was created for the 7 Day Roguelike Competition, but has since been expanded with magic items, spells, more classes and levels, as well as fast travelling to known locations, and high scores.
DiabloRL gives you a choice of classes, the Warrior, Rogue, or Sorcerer. Each of these has different starting and maximum stats, as well as completely different play styles.
- Website: [diablo.chaosforge.org][8]
- Authors: Kornel Kisielewicz, Chris Johnson and Mel'nikova Anastasia
- License: GNU GPL
- Version Number: 0.5.0
----------
![](http://www.linuxlinks.com/portal/content2/png/CataclysmDarkDaysAhead.png)
![](http://www.linuxlinks.com/portal/content/reviews/Games2/Screenshot-Cataclysm.png)
Cataclysm is an open source post-apocalyptic roguelike, set in the countryside of fictional New England after a devastating plague of monsters and zombies. It is a continuation of Whale's original Cataclysm, which expands it with numerous new creatures, buildings, gameplay mechanics and many other features.
While some have described it as a "zombie game", there's far more to Cataclysm than that. Struggle to survive in a harsh, persistent, procedurally generated world. Scavenge the remnants of a dead civilization for for food, equipment, or, if you're lucky, a vehicle with a full tank of gas to get you the hell out of Dodge. Fight to defeat or escape from a wide variety of powerful monstrosities, from zombies to giant insects to killer robots and things far stranger and deadlier, and against the others like yourself, that want what you have...
Cataclysm is very different from most roguelikes in many ways. Rather than being set in a vertical, linear dungeon, it is set in an unbounded, 3D world. This means that exploration plays a much bigger role than in most roguelikes, and the game is much less linear. As the map is so huge, it is actually completely persistant between games. If you die, and start a new character, your new game will be set in the same game world as your last. Like in many roguelikes, you will be able to loot the dead bodies of previous characters; unlike most roguelikes, you will also be able to retrace their steps completely, and any dramatic changes made to the world will persist into your next game.
- Website: [en.cataclysmdda.com][9]
- Authors: Kevin Granade
- License: Creative Commons Attribution-ShareAlike 3.0 Unported License
- Version Number: 0.A
--------------------------------------------------------------------------------
via: http://www.linuxlinks.com/article/20140621060017503/9ASCIIGames.html
译者:[译者ID](https://github.com/译者ID) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://sourceforge.net/apps/trac/unnethack/
[2]:http://www.catb.org/~esr/vms-empire/
[3]:http://mbays.freeshell.org/intricacy/
[4]:http://www.jwm-art.net/dark.php?p=XorCurses
[5]:http://goblinhack.sourceforge.net/
[6]:http://a-nikolaev.github.io/curseofwar/
[7]:https://sites.google.com/site/broguegame/
[8]:http://diablo.chaosforge.org/
[9]:http://en.cataclysmdda.com/

View File

@ -0,0 +1,91 @@
Advanced Directory Navigations Tips and Tricks in Linux
================================================================================
Directory navigation is one of the most basic concepts when it comes to understanding any command line system. Although its not a very difficult thing to understand when it comes to Linux, there are certain tips and tricks that can enhance your experience, and help you do things faster. In this article, we will discuss some advanced directory navigation tips.
### The Stuff We Already Know ###
Before jumping on to the advanced concepts, here is the basics of directory navigation that the article expects its readers to know:
- pwd command is used to display the current working directory.
- cd command is used to change the current working directory.
- cd followed by space and followed by a couple of periods (cd ..) brings the control back to the parent directory
- cd followed by just the name of a subdirectory changes to that subdirectory
- cd followed by a complete path changes to that directory
### Advanced Tips ###
In this section we will discuss some directory navigation tips and tricks that will help you easily switch between directories.
### Change to the home directory from anywhere ###
Your home directory is an important directory, and everyone switches back and forth quite frequently. While typing cd /home/<your-home-directory-name>, isnt a big deal, there is another way out which is not only easier but faster too. And that alternative is typing only cd.
Here is an example :
$ pwd
/usr/include/netipx
$ cd
$ pwd
/home/himanshu
So you can see, no matter where the current control is, just type cd command and you can immediately change to your home directory.
**NOTE**- To change to the home directory of a particular user, just type cd ~user_name'
### Switch between directories using cd - ###
Suppose your current working directory is this:
$ pwd
/home/himanshu/practice
and you want to switch to the directory **/usr/bin/X11**, and then switch back to the directory mentioned above. So what will you do? The most straight forward way is :
$ cd /usr/bin/X11
$ cd /home/himanshu/practice/
Although it seems a good way out, it really becomes tiring when the path to the directories is very long and complicated. In those cases, you can use the cd - command.
While using cd - command, the first step will remain the same, i.e., you have to do a cd <path> to the directory to you want to change to, but for coming back to the previous directory, just do a cd -, and thats it.
$ cd /usr/bin/X11
$ cd -
/home/himanshu/practice
$ pwd
/home/himanshu/practice
And if you want to again go back to the last directory, which in this case is /usr/bin/X11, run the cd - command again. So you can see that using cd - you can switch between directories easily. The only limitation is that it works with the last switched directories only.
### Switch between directories using pushd and popd ###
![directory navigation](http://linoxide.com/wp-content/uploads/2014/06/pushd-popd.jpg)
If you closely analyse the cd - trick, youll find that it helps switching between only the last two directories, but what if there is a situation in which you switch multiple directories, and then want to switch back to the first one. For example, if you switch from directory A to directory B, and then to directory C and directory D. Now, you want to change back to Directory A.
As a general solution, you can type cd followed by the path to directory A. But then again, if the path is long or complicated, the process can be time-consuming, especially when you have to switch between them frequently.
In these kind of situations, you can use the pushd and popd commands. The pushd command saves the path to a directory in memory, and the popd command removes it, and switches back to it too.
For example :
$ pushd .
/usr/include/netipx /usr/include/netipx
$ cd /etc/hp/
$ cd /home/himanshu/practice/
$ cd /media/
$ popd
/usr/include/netipx
$ pwd
/usr/include/netipx
So you can see that I used pushd command to save the path to current working directory (represented by .), and then changed multiple directories. To come back to the saved directory, I just executed the popd command.
**NOTE**- You can also use pushd command to switch back to the saved directory, but that doesnt remove it from the memory, like popd does.
--------------------------------------------------------------------------------
via: http://linoxide.com/linux-command/directory-navigations-tips-tricks/
译者:[译者ID](https://github.com/译者ID) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出

View File

@ -0,0 +1,87 @@
How to speed up directory navigation in a Linux terminal
================================================================================
As useful as navigating through directories from the command line is, rarely anything has become as frustrating as repeating over and over "cd ls cd ls cd ls ..." If you are not a hundred percent sure of the name of the directory you want to go to next, you have to use ls. Then use cd to go where you want to. Hopefully, a lot of terminals and shell languages now propose a powerful auto-completion feature to cope with that problem. But it remains that you have to hit the tabulation key frenetically all the time. If you are as lazy as I am, you will be very interested in autojump. autojump is a command line utility that allows you to jump straight to your favorite directory, regardless of where you currently are.
### Install autojump on Linux ###
To install autojump on Ubuntu or Debian:
$ sudo apt-get install autojump
To install autojump on CentOS or Fedora, use yum command. On CentOS, you need to [enable EPEL repository][1] first.
$ sudo yum install autojump
To install autojump on Archlinux:
$ sudo pacman -S autojump
If you cannot find a package for your distribution, you can always compile from the sources on [GitHub][2].
### Basic Usage of autojump ###
The way autojump works is simple: it records your current location every time you launch a command, and adds it in its database. That way, some directories will be added more than others, typically your most important ones, and their "weight" will then be greater.
From there you can jump straight to them using the syntax:
autojump [name or partial name of the directory]
Notice that you do not need a full name as autojump will go through its database and return its most probable result.
For example, assume that we are working in a directory structure such as the following.
![](https://farm4.staticflickr.com/3921/14276240117_9f56b42fec_z.jpg)
Then the command below will take you straight to /root/home/doc regardless of where you were.
$ autojump do
If you hate typing too, I recommend making an alias for autojump or using the default one.
$ j [name or partial name of the directory]
Another notable feature is that autojump supports both zsh shell and auto-completion. If you are not sure of where you are about to jump, just hit the tabulation key and you will see the full path.
So keeping the same example, typing:
$ autojump d
and then hitting tab will return either /root/home/doc or /root/home/ddl.
Finally for the advanced user, you can access the directory database and modify its content. It then becomes possible to manually add a directory to it via:
$ autojump -a [directory]
If you suddenly want to make it your favorite and most frequently used folder, you can artificially increase its weight by launching from within it the command
$ autojump -i [weight]
This will result in this directory being more likely to be selected to jump to. The opposite would be to decrease its weight with:
$ autojump -d [weight]
To keep track of all these changes, typing:
$ autojump -s
will display the statistics in the database, while:
$ autojump --purge
will remove from the database any directory that does not exist anymore.
To conclude, autojump will be appreciated by all the command line power users. Whether you are ssh-ing into a server, or just like to do things the old fashion way, reducing your navigation time with fewer keystrokes is always a plus. If you are really into that kind of utilities, you should definitely look into [Fasd][3] too, which deserves a post in itself.
What do you think of autojump? Do you use it regularly? Let us know in the comments.
--------------------------------------------------------------------------------
via: http://xmodulo.com/2014/06/speed-up-directory-navigation-linux-terminal.html
译者:[译者ID](https://github.com/译者ID) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://xmodulo.com/2013/03/how-to-set-up-epel-repository-on-centos.html
[2]:https://github.com/joelthelion/autojump
[3]:https://github.com/clvv/fasd

View File

@ -1,369 +0,0 @@
编写属于你的第一个Linux内核模块
================================================================================
> 曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引你走向前方……
内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中它八成就是了。Linux内核和它的用户空间是大不相同的抛开漫不经心你必须小心翼翼因为你编程中的一个bug就会影响到整个系统。浮点数学做起来可不容易堆栈固定而渺小而你写的代码总是异步的因此你需要想想怎样让它并发。而除了所有这一切之外Linux内核只是一个很大的、很复杂的C程序它对每个人开放任何人都去读它、学习它并改进它而你也可以是其中之一。
> “开始内核编程的最简单的方式
> 是写模块——一段代码
> 可以用来动态加载进内核。”
可能开始内核编程的最简单的方式就是写模块——一段可以动态加载进内核并从内核移除的代码。模块所能做的事是有限的——例如他们不能添加或移除像进程描述符这样的常规数据结构域。但是在其它方面他们是成熟的内核级的代码可以在需要时随时编译进内核这样就可以摒弃所有的限制了。完全可以在Linux源代码树以外来开发并编译一个模块这并不奇怪它称为树外开发如果你只是想稍微玩玩而并不想提交修改以包含到主线内核中去这样的方式是很方便的。
在本教程中,我们将开发一个简单的内核模块用以创建一个**/dev/reverse**设备。写入该设备的字符串将以逆序的方式读回“Hello World”读成“World Hello”。这是一个流行的节目采访智力游戏而当你展示能力来实施时你也可能获得一些奖励分。在开始前有一句忠告你的模块中的一个bug会导致系统崩溃虽然可能性不大但还是有可能的和数据丢失。在开始前请确保你已经将重要数据备份或者采用一种更好的方式在虚拟机中进行试验。
### 尽可能避免root身份 ###
> 默认情况下,**/dev/reverse**只有root可以使用因此你不得不使用**sudo**来测试该程序。要解决该问题,可以创建一个包含以下内容的**/lib/udev/rules.d/99-reverse.rules**文件:
>
> SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"
>
> 别忘了重新插入模块。让设备节点让非root用户访问这往往不是一个好主意但是在开发其间却是十分有用的这不是说以root身份运行二进制测试文件也不是个好主意。
#### 模块的构造 ####
由于大多数的Linux内核模块是用C写的除了低级别特定架构部分所以推荐你将模块以单一文件形式保存例如reverse.c。我们已经把完整的源代码放在GitHub上——这里我们将看其中的一些片段。开始时我们先要包含一些常见的文件头并用预定义的宏来描述模块
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Valentine Sinitsyn <valentine.sinitsyn@gmail.com>");
MODULE_DESCRIPTION("In-kernel phrase reverser");
这里一切都直接明了,除了**MODULE_LICENSE()**它不仅仅是一个标记。内核坚定地支持GPL兼容代码因此如果你把许可证设置为其它非GPL兼容的“专利”特定的内核功能将在你的模块中不可用。
### 什么时候不该写内核模块 ###
> 内核编程很有趣,但是在现实项目中写(尤其是调试)内核代码要求特定的技巧。通常来讲,在没有其它方式解决你的问题时,你才应该沉入内核级别。可能你可以待在用户空间中,如果:
> - 你开发一个USB驱动 —— 请查看[libusb][1]。
> - 你开发一个文件系统 —— 试试[FUSE][2]。
> - 你在扩展Netfilter —— 那么[libnetfilter_queue][3]对你有所帮助。
>
> 通常,本地内核代码会干得更好,但是对于许多项目而言,这点性能丢失并不严重。
由于内核编程总是异步的没有Linux顺序执行得**main()**函数来运行你的模块。取而代之的是,你为各种事件提供了回调函数,像这个:
static int __init reverse_init(void)
{
printk(KERN_INFO "reverse device has been registered\n");
return 0;
}
static void __exit reverse_exit(void)
{
printk(KERN_INFO "reverse device has been unregistered\n");
}
module_init(reverse_init);
module_exit(reverse_exit);
这儿,我们定义了函数,用来访问模块的插入和移除功能,只有第一个是必要的。目前,它们只是打印消息到内核环缓冲区(可以通过**dmesg**命令从用户空间访问);**KERN_INFO**是日志等级(注意,没有逗号)。**_init**和**_exit**是属性 —— 联结到函数的元数据片或者变量。属性在用户空间的C代码中是很罕见的但是内核中却很普遍。所有标记为**_init**的,会在初始化后再生(还记得那条老旧的“释放未使用的内核内存……”信息?)。**__exit**表明,当代码被静态构建进内核时,该函数可以安全地优化。最后,**module_init()**和**module_exit()**这两个宏将**reverse_init()**和**reverse_exit()**函数设置成为我们模块的生命周期回调函数。实际的函数名称并不重要,你可以称它们为**init()**和**exit()**,或者**start()**和**stop()**,你想叫什么就叫什么吧。在你的模块外,它们被申明成为静态的和不可见的。事实上,内核中的任何函数都是不可见的,除非明确地被导出。然而,在内核程序员中,给你的函数加上模块名前缀是约定俗成的。
这些是基本要素 —— 让我们把事情变得更有趣些。模块可以接收参数,就像这样:
# modprobe foo bar=1
**modinfo**命令显示了所有模块接受的参数,而这些也可以在**/sys/module//parameters**下作为文件使用。我们的模块需要一个缓冲区来存储短语 —— 让我们把这大小设置为用户可配置。添加**MODULE_DESCRIPTION()**以下的三行:
static unsigned long buffer_size = 8192;
module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));
MODULE_PARM_DESC(buffer_size, "Internal buffer size");
这儿我们定义了一个变量来存储该值将其包裹到一个参数中并通过sysfs来让所有人可读。参数的描述最后一行会出现在modinfo的输出中。
由于用户可以直接设置**buffer_size**,我们需要在**reverseinit()**来清除它。你总该检查来自内核外的数据 —— 如果你不这么做,你就是会将你自身置于内核异常之中,设置造成安全漏洞。
static int __init reverse_init()
{
if (!buffer_size)
return -1;
printk(KERN_INFO
"reverse device has been registered, buffer size is %lu bytes\n",
buffer_size);
return 0;
}
来自模块初始化函数的非0返回值意味着模块执行失败。
### 导航 ###
> 但你开发模块时Linux内核就是你所需一切的源头。然而它相当大你可能在查找你所要的内容时会有困难。幸运的是在浏览庞大的代码库时有工具可以帮助你干得轻松一点。首先是Cscope —— 在终端中运行的一个令人肃然起敬的工具。你所要做的,就是在内核源代码的顶级目录中运行**make cscope && cscope**。Cscope和Vim以及Emacs整合得很好因此你可以在使用你最喜爱的编辑器舒适地工作时来使用它。
> 如果基于终端的工具不是你的最爱,那么就访问[http://lxr.free-electrons.com][4]吧。它是一个基于web的内核导航工具即使它的功能没有Cscope来得多例如你不能方便地找到函数的用法但它仍然提供了足够多的快速查询功能。
现在是时候来编译模块了。你将需要用于正在运行的内核版本的头文件(**linux-headers**,或者同等软件包)和**build-essential**或者类似的包。接下来该创建一个标准的Makefile模板
obj-m += reverse.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
现在,调用**make**来构建你的第一个模块。如果你输入的都正确在当前目录内会发现reverse.ko文件。使用**sudo insmod reverse.ko**插入,然后运行:
$ dmesg | tail -1
[ 5905.042081] reverse device has been registered, buffer size is 8192 bytes
恭喜了!然而,目前这一行还只是在逗你玩而已 —— 还没有设备节点呢。让我们来修复它。
#### 混杂设备 ####
在Linux中有一种特殊的字符设备类型叫做“混杂设备”或者简称为“misc”。它设计用于只有一个单一接入点的小型设备驱动而这正是我们所需要的。所有混杂设备共享同一个主设备号10因此一个驱动(**drivers/char/misc.c**)就可以查看它们所有设备了,而这些设备用次设备号来区分。在所有其它意义上,它们只是普通字符设备。
要为该设备注册一个次设备号(以及一个接入点),你需要声明**struct misc_device**,填上所有字段(注意语法),然后使用指针指向该结构函数来调用**misc_register()**。为了这个能工作,你也需要包含**linux/miscdevice.h**头文件:
static struct miscdevice reverse_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "reverse",
.fops = &reverse_fops
};
static int __init reverse_init()
{
...
misc_register(&reverse_misc_device);
printk(KERN_INFO ...
}
这儿我们为名为“reverse”的设备请求一个第一个可用的动态的次设备号省略号表明我们已经见过的省略的代码。别忘了在模块卸下后注销掉该设备。
static void __exit reverse_exit(void)
{
misc_deregister(&reverse_misc_device);
...
}
fops字段存储了一个指针指向结构函数**file_operations**在Linux/fs.h中已声明而这真是我们模块的接入点。**reverse_fops**定义如下:
static struct file_operations reverse_fops = {
.owner = THIS_MODULE,
.open = reverse_open,
...
.llseek = noop_llseek
};
再者,**reverse_fops**包含了一系列回调函数(也称之为方法),当用户空间代码打开一个设备时,就会执行。从该设备读取,向该设备写入,或者关闭文件描述符。如果你忽略了所有这些,就会使用一个灵敏的回调函数来替代。这就是为什么我们明确给**noop_llseek()**设置了**llseek**方法,而它却什么也不干(就像名称中暗指的)。默认部署改变了文件指针,我们现在也不想我们的设备被找到(这是你们的今天的回家作业)。
#### 我在关闭时打开 ####
让我们实施该方法。我们将分配一个新的缓冲区给每个打开的文件描述符并在它关闭时释放。这事实上并不安全如果一个用户空间应用程序泄漏了描述符也许是故意的它就会霸占RAM并使系统不可用。在现实世界中你总得考虑到这些可能性。但在本教程中这种方法可以接受。
我们需要一个结构函数来描述缓冲区。内核提供了许多常规的数据结构链接列表双联的哈希表树等等之类。然而缓冲区常常从零开始实施。我们将调用我们的“struct buffer”
struct buffer {
char *data, *end, *read_ptr;
unsigned long size;
};
**data**是该缓冲区存储的一个指向字符串的指针,而最后部分是字符串结尾后的第一个字节。**read_ptr**是**read()**开始读取数据的地方。缓冲区大小为了完整性而存储 —— 目前,我们还没有使用该区域。你不能假设使用你结构体的用户会正确地初始化所有这些东西,所以最好在函数中封装缓冲区分配和解除。它们通常命名为**buffer_alloc()**和**buffer_free()**。
static struct buffer *buffer_alloc(unsigned long size)
{
struct buffer *buf;
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (unlikely(!buf))
goto out;
...
out:
return buf;
}
内核内存使用**kmalloc()**来分配,并使用**kfree()**来释放;**kzalloc()**的风格是将内存设置为全零。不同于标准的**malloc()**,它的内核对应部分收到的标志指定了第二个参数中请求的内存类型。这里,**GFP_KERNEL**是说我们需要一个普通的内核内存不是在DMA或高内存中以及函数可以按需睡眠重新编排进程。**sizeof(*buf)**是一种常见的方式,它用来获取可通过指针访问的结构体的大小。
你应该随时检查**kmalloc()**的返回值解应用NULL指针将导致内核异常。同时也需要注意**unlikely()**宏的使用。它(及其相对宏**likely()**)被广泛用于内核中,用于表明条件几乎总是真的(或假的)。它不会影响到控制流,但是能帮助现代处理器通过分支预测技术来提升性能。
最后,注意**gotos**。它们常常为认为是邪恶的但是Linux内核以及一些其它系统软件采用它们来实施集中式的函数退出。这样的结果是减少嵌套深度使代码更具可读性而且非常像更高级语言中的**try-catch**区块。
有了**buffer_alloc()**和**buffer_free()****open**和**close**方法就变得很简单了。
static int reverse_open(struct inode *inode, struct file *file)
{
int err = 0;
file->private_data = buffer_alloc(buffer_size);
...
return err;
}
**struct file**是一个标准的内核数据结构,用以存储打开的文件的信息,如当前文件位置(**file->fpos**),标志(**file->flags**),或者打开模式(**file->fmode**)。另外一个字段**file->privatedata**用于关联文件到一些专有数据它的类型是void *,而且它在文件拥有者以外对内核不透明。我们将一个缓冲区存储在那里。
如果缓冲区分配失败,我们通过返回否定值(**-ENOMEM**)来为调用的用户空间代码标明。
#### 学会读写 ####
“read”和“write”方法是真正完成工作的地方。当数据写入到缓冲区时我们就丢弃它里头先前的内容并在没有任何临时存储时将短语恢复原状。**read**方法仅仅是从内核缓冲区复制数据到用户空间。但是如果缓冲区还没有数据,**reverseread()**会做什么呢?在用户空间中,**read()**调用会在有可用数据前阻塞它。在内核中你必须等待。幸运的是有一项机制用于处理这种情况就是wait queues
想法很简单。如果当前进程需要等待某个事件,它的描述符(**struct task_struct**存储为current被放进非可运行睡眠中状态并添加到一个队列中。然后**schedule()**就被调用来选择另一个进程运行。生成事件的代码通过使用队列将等待进程放回**TASKRUNNING**状态来唤醒它们。调度程序将在以后在某个地方选择它们之一。Linux有多种非可运行状态最值得注意的是**TASKINTERRUPTIBLE**(一个可以通过信号中断的睡眠)和**TASKKILLABLE**(一个可被杀死的睡眠中的进程)。所有这些都应该正确处理,并等待队列为你做这些事。
一个用以存储读取等待队列头的天然场所就是结构缓冲区,所以从为它添加**wait_queue_head_t read_queue**字段开始。你也应该包含**linux/sched.h**。可以使用DECLARE_WAITQUEUE()宏来静态声明一个等待队列。在我们这种情况下,需要动态初始化,因此添加下面这行到**buffer_alloc()**
init_waitqueue_head(&buf->read_queue);
我们等待可用数据;或者等待**read_ptr != end**条件成立。我们也想要让等待操作可以被中断通过Ctrl+C。因此“read”方法应该像这样开始
static ssize_t reverse_read(struct file *file, char __user * out,
size_t size, loff_t * off)
{
struct buffer *buf = file->private_data;
ssize_t result;
while (buf->read_ptr == buf->end) {
if (file->f_flags & O_NONBLOCK) {
result = -EAGAIN;
goto out;
}
if (wait_event_interruptible
(buf->read_queue, buf->read_ptr != buf->end)) {
result = -ERESTARTSYS;
goto out;
}
}
...
我们让它循环,直到有可用数据,如果没有则使用**wait_event_interruptible()**(它是一个宏,不是函数,这就是为什么要给队列传递值)来等待。好吧,如果**wait_event_interruptible()**被中断它返回一个非0值这个值代表**-ERESTARTSYS**。这段代码意味着系统调用应该重新启动。**file->f_flags**检查以非阻塞模式打开的文件数:如果没有数据,返回**-EAGAIN**。
我们不能使用**if()**来替代**while()**,因为可能有许多进程正等待数据。当**write**方法唤醒它们时,调度程序选择一个来以不可预知的方式运行,因此,在这段代码有机会执行的时候,缓冲区可能再次空出。现在,我们需要将数据从**buf->data** 复制到用户空间。**copytouser()**内核函数就干了此事:
size = min(size, (size_t) (buf->end - buf->read_ptr));
if (copy_to_user(out, buf->read_ptr, size)) {
result = -EFAULT;
goto out;
}
如果用户空间指针错误,那么调用可能会失败;如果发生了此事,我们就返回**-EFAULT**。记住,不要相信任何来自内核外的事物!
buf->read_ptr += size;
result = size;
out:
return result;
}
为了让数据能读入到专有组块中,需要进行简单运算。该方法返回读入的字节数,或者一个错误代码。
写方法更简短。首先,我们检查缓冲区是否有足够的空间,然后我们使用**copy_from_userspace()**函数来获取数据。再然后**read_ptr**和结束指针会被重置,缓冲区内容会被撤销掉:
buf->end = buf->data + size;
buf->read_ptr = buf->data;
if (buf->end > buf->data)
reverse_phrase(buf->data, buf->end - 1);
这里, **reverse_phrase()**干了所有吃力的工作。它依赖于**reverse_word()**函数,该函数相当简短并且标记为内联。这是另外一个常见的优化;但是,你不能过度使用。因为积极的内联会导致内核映像徒然增大。
最后,我们需要唤醒**read_queue**中等待数据的进程,就跟先前讲过的那样。**wake_up_interruptible()**就是用来干此事的:
wake_up_interruptible(&buf->read_queue);
唷!你现在已经有了一个内核模块,它至少已经编译成功了。现在,是时候来测试了。
### 调试内核代码 ###
> 或许,内核中最常见的调试方法就是打印。如果你愿意,你可以使用普通的**printk()** (假定使用**KERN_DEBUG**日志等级。然而那儿还有更好的办法。如果你正在写一个设备驱动这个设备驱动有它自己的“struct device”可以使用**pr_debug()**或者**dev_dbg()**:它们支持动态调试(**dyndbg**)特性,并可以根据需要启用或者禁用(请查阅**Documentation/dynamic-debug-howto.txt**)。对于单纯的开发消息,使用**prdevel()**该函数没有操作符除非设置了DEBUG。要为我们的模块启用DEBUG请添加以下行到Makefile中
> CFLAGS_reverse.o := -DDEBUG
>
> 完了之后,使用**dmesg**来查看**pr_debug()**或**pr_devel()**生成的调试信息。
> 或者,你可以直接发送调试信息到控制台。要想这么干,你可以设置**console_loglevel**内核变量为8或者更大的值**echo 8 /proc/sys/kernel/printk**),或者在高日志等级,如**KERN_ERR**,来临时打印要查询的调试信息。很自然,在发布代码前,你应该移除这样的调试声明。
> 注意出现在控制台的内核消息而不要在Xterm这样的终端模拟器窗口中去查看那也是你在内核开发时经常会建议你不要再X环境下进行的原因。
### 惊喜,惊喜! ###
编译模块,然后加载进内核:
$ make
$ sudo insmod reverse.ko buffer_size=2048
$ lsmod
reverse 2419 0
$ ls -l /dev/reverse
crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse
一切似乎就位。现在,要测试模块是否正常工作,我们将写一段小程序来翻转它的第一个命令行参数。**main()**(没有错误检查)可能看上去像这样:
int fd = open("/dev/reverse", O_RDWR);
write(fd, argv[1], strlen(argv[1]));
read(fd, argv[1], strlen(argv[1]));
printf("Read: %s\n", argv[1]);
像这样运行:
$ ./test 'A quick brown fox jumped over the lazy dog'
Read: dog lazy the over jumped fox brown quick A
它工作正常!玩得更逗一点:试试传递单个单词或者单个字母的短语,空的字符串或者是非英语字符串(如果你有这样的键盘布局设置),以及其它任何东西。
现在,让我们让事情变得更好玩一点。我们将创建两个进程,它们共享一个文件描述符(因而还有内核缓冲区)。其中一个会持续写入字符串到设备,而另一个将读取这些字符串。在下例中,我们使用了**fork(2)**系统调用而pthreads也很好用。我也忽略了打开和关闭设备以及错误检查部分的代码又来了
char *phrase = "A quick brown fox jumped over the lazy dog";
if (fork())
/* Parent is the writer */
while (1)
write(fd, phrase, len);
else
/* child is the reader */
while (1) {
read(fd, buf, len);
printf("Read: %s\n", buf);
}
你希望这个程序会输出什么呢?下面就是在我的笔记本上得到的东西:
Read: dog lazy the over jumped fox brown quick A
Read: A kcicq brown fox jumped over the lazy dog
Read: A kciuq nworb xor jumped fox brown quick A
Read: A kciuq nworb xor jumped fox brown quick A
...
这里发生了什么呢?举行了一场比赛。我们认为**read**和**write**是很小的,或者从头到尾一次执行一个指令。然而,内核是并发的野兽,它可以很容易地重排**reverse_phrase()**函数内部某个地方运行着的内核模式部分的写入操作。如果进行**read()**操作的进程在写入操作结束前就被编排进去就会产生数据不连续状态。这些bug非常难以排除。但是怎样来处理这个问题呢
基本上,我们需要确保在写方法返回前没有**read**方法能被执行。如果你曾经编写过一个多线程的应用程序你可能见过同步原语如互斥锁或者信号。Linux也有这些但有些细微的差别。内核代码可以运行在进程条件中“代表”用户空间代码工作就像我们的方法那样以及运行在中断条件中例如在IRQ处理器中。如果你的程序处于进程条件中并且你需要的锁已经被拿走你的程序就会睡眠并重试直至成功。在中断条件中是无法睡眠的因此代码在循环中流转直到有可用的锁为止。关联原语被称为自旋锁但在我们的环境中一个简单的互斥锁 —— 在特定时间内只有唯一一个进程能“占有”的对象 —— 就足够了。处于性能方面的考虑,现实的代码可能也会使用读-写信号。
锁总是保护某些数据在我们的环境中是一个“struct buffer”实例而且也常常会把它们嵌入到它们所保护的结构体中。因此我们添加一个互斥锁struct mutex lock到“struct buffer”中。我们也必须用**mutex_init()**来初始化互斥锁;**buffer_alloc**是用来处理这件事的好地方。使用互斥锁的代码也必须包含**linux/mutex.h**。
互斥锁很像交通信号灯 —— 除非驱动查看并跟踪信号,否则它没什么用。因此,在对缓冲区做操作并在操作完成时释放它之前,我们需要更新**reverse_read()**和**reverse_write()**来获取互斥锁。让我们来看看**read**方法 —— **write**的工作原理相同:
static ssize_t reverse_read(struct file *file, char __user * out,
size_t size, loff_t * off)
{
struct buffer *buf = file->private_data;
ssize_t result;
if (mutex_lock_interruptible(&buf->lock)) {
result = -ERESTARTSYS;
goto out;
}
我们在函数一开始就获取锁。**mutex_lock_interruptible()**要么抓取互斥锁然后返回,要么让进程睡眠,直到有可用的互斥锁。就像前面一样,**_interruptible**后缀意味着睡眠可以由信号来中断。
while (buf->read_ptr == buf->end) {
mutex_unlock(&buf->lock);
/* ... wait_event_interruptible() here ... */
if (mutex_lock_interruptible(&buf->lock)) {
result = -ERESTARTSYS;
goto out;
}
}
下面是我们的“等待数据”循环。当持有互斥锁,或者发生称之为“死锁”的情境时,不应该让进程睡眠。因此,如果没有数据,我们释放互斥锁并调用**wait_event_interruptible()**。当它返回时,我们重新获取互斥锁并像往常一样继续:
if (copy_to_user(out, buf->read_ptr, size)) {
result = -EFAULT;
goto out_unlock;
}
...
out_unlock:
mutex_unlock(&buf->lock);
out:
return result;
最后,当函数结束,或者在互斥锁被占有过程中发生错误时,互斥锁被解锁。重新编译模块(别忘了重新加载),然后再次进行测试。现在你应该没发现毁坏的数据了。
### 接下来是什么? ###
现在你体验了一把内核侵入。我们刚刚为你揭开了今天话题的外衣里面还有更多东西供你探索。我们的第一个模块是有意识地写得简单一点在从中学到的概念在更复杂的环境中也一样。并发、方法表、注册回调函数、使进程睡眠以及唤醒进程这些都是内核黑客们耳熟能详的东西而现在你已经看过了它们的运作。或许某天你的内核代码也将被加入到主线Linux源代码树中 —— 如果真这样,请联系我们!
--------------------------------------------------------------------------------
via: http://www.linuxvoice.com/be-a-kernel-hacker/
译者:[GOLinux](https://github.com/GOLinux) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://www.libusb.org/
[2]:http://fuse.sf.net/
[3]:http://www.linuxvoice.com/be-a-kernel-hacker/www.netfilter.org/projects/libnetfilter_queue
[4]:http://lxr.free-electrons.com/

View File

@ -227,7 +227,7 @@ fdisk和sfdisk显示完整的大量的可以花些时间来解释的信息
--------------------------------------------------------------------------------
via: http://www.binarytides.com/linux-command-check-disk-partitions/
译者:[tenght](https://github.com/tenght) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出

View File

@ -1,495 +0,0 @@
如何编写你的第一个Linux内核模块
================================================================================
>你是否曾经想过开始去攻击一个内核?然而却不知道从何开始?让我们来告诉你该怎么做…
内核程序通常被视为一个黑色魔术。从Arthur C Clarke的理念来说确实是这样。Linux内核与用户空间的确存在很多不同许多抽象的东西被搁置你需要格外小心当一个bug存在代码中时将会影响到整个系统。这不是一个简单的方法去改浮点数堆栈既被固定了而且很小你写的代码通常是异步的所以你需要考虑到并发性的问题。尽管如此Linux内核是一个庞大且复杂的C程序对每个人都提供开源阅读、学习和改进你也可以成为其中的一份子。
> “The easiest way to start kernel programming
> is to write a module a piece of code that
> can be dynamically loaded into the kernel.”
> “最简单的方法来开始写内核程序
> 就是写一个内核模块 - 这段代码
> 可以动态加载到内核中。”
可能开始学习内核程序的最简单方式来就是先编写一个模块——一段代码可以动态加载到你的内核和卸载。但是也存在一些限制比如说什么模块可以这样做——例如它们不能添加或者删除常用数据结构的一些字段例如过程描述。但是在其它方面它们是成熟的内核级别代码根据需要并经常被编译到内核中因此删除所有受限。在Linux源代码树开发和编译一个模块这不出乎医疗应该成为out-of-tree构建是完全有可以的如果你只想玩一下而不去提交你主线内核的改动这是非常方便的。
在这个教程中,我们将开发一个简单的内核模块,创建**/dev/reverse**设备。当字符写到这个设备就回次序颠倒读出来就像“Hello World”会变成“World Hello”。这是一个很受欢迎的程序员面试难题当你利用自己的能力在内核级别实现这个功能时可以使你得到一些加分。在我们开始之前会有一句提示有一个bug寸在你的模块有可能导致你的系统崩溃不太可能但是可能和数据丢失。
###尽可能避开root用户 ###
>默认情况下,**/dev/reverse**只能对根用户可用,所以你只能通过**sudo**来运行你的测试程序。为了修复这个问题,创建一个**/lib/udev/rules.d/99-reverse.rules**文件,其中包含:
>
> SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"
>不要忘记重新插入模块。通常来说使设备接点可访问对非根用户来说通常不是一个好主意,但是在开发过程中的确非常有用。更不值得一提的是作为根用户运行测试二进制文件也不是一个好主意。
#### 关于模块的剖析 ####
由于大多数Linux内核模块都是用C编写的除了底层的特定于体系结构的部分所以建议把你的内核保存在一个独立的文件中叫做reverse.c).我们会将整份源代码放在GitHub中——在这里不我们可以看到一些小片段。首先我们要把一些常见的头文件和描述该模块使用预定义的宏包含进来
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Valentine Sinitsyn <valentine.sinitsyn@gmail.com>");
MODULE_DESCRIPTION("In-kernel phrase reverser");
这一切其实都很简单,除了**MODULE_LICENSE()**之外这不是一个纯粹的标记。内核尤其钟情于GPLcompatible 代码所以如果你设置一些非GPL兼容的认证也就是说“私有认证”某些特定的内核功能在你的模块中将不可用。
### 什么时候不该一个内核模块 ###
>- 你开发一个USB驱动时 - 需要查阅[libusb][1].
>- 你开发一个系统文件时 - 试试 [FUSE][2].
>- 你扩展Netfilter - [libnetfilter_queue][3] 也许能够对你有所帮助。
>通常,本地代码会运行得更好,但是很多程序来说这种情况并不是至关重要的。
由于内核程序通常是异步的,它并没有 **main()** 功能Linux是按顺序运行你的模块。取而代之你将会提供各种事件的回滚例如
static int __init reverse_init(void)
{
printk(KERN_INFO "reverse device has been registered\n");
return 0;
}
static void __exit reverse_exit(void)
{
printk(KERN_INFO "reverse device has been unregistered\n");
}
module_init(reverse_init);
module_exit(reverse_exit);
这里,我们定义的函数被称为模块的插入和删除。只有第一个是必要的。目前,他们简单地发送一条内核指令给缓存中(可以通过用户界面发送 **dmesg** 命令);**KERN_INFO**是一个日至级别(切记这里没有逗号).**__init** 和 **__exit** 都是特性 - 把元数据添加到函数或者变量。在用户界面中C代码的属性是很罕见的但是在内核中却很普遍。标有**__init**都会经过初始化以后被回收(要记得之前释放未使用的内核内寸...这条消息?)。**__exit** 表示,当代码静态构建到内核中时,函数会安全地优化。最后,**module_init()** 和**module_exit()**宏设置**reverse_init()** 和 **reverse_exit()**作为我们模块生命周期的回滚。实际的函数名称并不重要;你可以按着你的想法命名为**init()** 和**exit()** 或者 **start()****stop()**。他们都是静态声明,你在外部模块是看不到的。实际上,许多内核中的模块是可视的除非是明显的导出。然而,在函数前面添加你的函数作为前缀通常是内核程序员约定俗成的做法。
这些都是些基本概念 - 让我们来做更多有趣的事情吧。模块可以添加参数,例如:
# modprobe foo bar=1
**modinfo**显示了所有模块可以接受的参数,这些也可以在**/sys/module//parameters**文件中找到。我们的模块需要一个缓存区来存储参数 - 让我们来调整大小使用户可配置。在**MODULE_DESCRIPTION()**添加如下三行:
static unsigned long buffer_size = 8192;
module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));
MODULE_PARM_DESC(buffer_size, "Internal buffer size");
这里我们定义一个变量来存储值封装成一个参数使得每个人都可以通过sysfs对其进行阅读。这个参数的描述在最后一行出现在modinfo的输出里。
由于用户可以直接设置**buffer_size**,我们需要在**reverse_init()**中对其进行清理。你应该经常检查来自内核意外数据 - 付过你不这样做,你将是在给自己制造内核异常甚至是安全漏洞。
static int __init reverse_init()
{
if (!buffer_size)
return -1;
printk(KERN_INFO
"reverse device has been registered, buffer size is %lu bytes\n",
buffer_size);
return 0;
}
来自模块初始化函数的非0返回值意味着执行失败。
### 导航 ###
>当你正在开发模块时linux内核就是无限的源头。然而它确实很大你也许在寻找的过程中会遇到很多困难。幸运的是在庞大的代码库面前有许多工具使这个过程变得简单。首先有一个叫Cscope的工具——在终端运行中一个比较经典的工具。简单地运行**make cscope && cscope**在源代码一级目录。Cscope很好地集成了Vim和Emacs于是你又可以不离开你喜爱地编辑工具。
>基于终端工具的使用也许不是你的强项,请访问[http://lxr.free-electrons.com][4].这是一个基于网站的内核导航工具没有太多的类似Cscope的特性例如你不能轻易地查找函数的用法但是它还是可以给你提供快速的查找。
现在,是时候编译模块了。你需要你正在运行的内核版本头文件(**linux-headers**或者同等包)和**build-essential** 或者类是。接下来是时候创建一个叫Makefile的样板
obj-m += reverse.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
现在,使用**make**来创建你的第一个模块。如果你正确输入,你将会找到在当前目录里找到 **reverse.ko**。插入**sudo insmod reverse.ko**并运行:
$ dmesg | tail -1
[ 5905.042081] reverse device has been registered, buffer size is 8192 bytes
祝贺你!可是,现在这一行告诉你一个假象 —— 这里并不存在设备接点。让我们修复它吧。
#### 混杂设备 ####
在Linux中有一种特殊的字符设备类型成为“miscellaneous”简写“misc”。这是专为单点小型设备驱动而涉及的而且能恰好满足我们的需求。所有的misc设备共享相同的主设备号10所以一个驱动程序(**drivers/char/misc.c**)可以查看到他们的所有设备,你可以从他们的次设备号分辨出他们。从其他意义来说,他们也只是普通的字符设备。
为了给设备注册一个次要设备号(和一个接入点),你需要先声明**struct misc_device**,填写字段(注意语法),并调用**misc_register()**这个结构的指针。为此,你还需要把**linux/miscdevice.h** 头文件包含进去:
static struct miscdevice reverse_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "reverse",
.fops = &reverse_fops
};
static int __init reverse_init()
{
...
misc_register(&reverse_misc_device);
printk(KERN_INFO ...
}
在这里我们要求第一个可用的动态的次设备号命名为“reverse”;省略号表示已经省略了的代码,这些省略了的代码是我们之前看过的。别忘了在模块卸载后注销该设备:
static void __exit reverse_exit(void)
{
misc_deregister(&reverse_misc_device);
...
}
fop字段存储了一个指向**file_operations**在linux/fs.h中声明结构的指针这是指向我们模块的接入点。**reverse_fops**定义如下:
static struct file_operations reverse_fops = {
.owner = THIS_MODULE,
.open = reverse_open,
...
.llseek = noop_llseek
};
另外,**reverse_fops**包含了一组将要执行的回调函数(也成为方法),当用户空间代码打开了一个设备,从中进行读写或者关闭文件描述。如果你忽略了这些,那么将会有一个只能的回退来取代它。这就是我们要明确地给**noop_llseek()**设置**llseek**的方法,(顾名思义)而它却什么都不干。默认地实现是改变了文件的指针,我们也不想我们的设备被识别(这将是你们今天的家庭作业)。
#### 在结束时开启 ####
让我们来实现这个方法。我们将会为每一个文件描述的打开分配一个新的缓冲区来给它自由关闭。这确实是不安全:如果一个用户空间应用的漏洞描述(或者是故意的),但它可能占用着内存,导致系统无法正常使用。在真实应用中,你应该经常考虑到这些可能性。但是在本教程中,这个方法是可接受的。
我们需要一个结构来描述缓冲区。内核提供了许多通用的数据结构链表双键链表哈希表树等等。然而缓冲区通常从零开始实现的。我们将调用我们的“struct buffer”:
struct buffer {
char *data, *end, *read_ptr;
unsigned long size;
};
**data**是一个寸在缓冲区指向字符串的指针数据,而结尾是在字符串结束后的第一个字节。缓冲区的大小是为完整性而存储的 —— 目前,我们不使用该字段。你不应该认为你的用户结构会正确地初始化所有这些,所以你应更好地在函数中封装缓冲区和重新分配分配。他们通常命名为**buffer_alloc()** 和 **buffer_free()**.
static struct buffer *buffer_alloc(unsigned long size)
{
struct buffer *buf;
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (unlikely(!buf))
goto out;
...
out:
return buf;
}
Kernel memory is allocated with **kmalloc()** and freed with **kfree()**; the **kzalloc()** flavour sets the memory to all-zeroes. Unlike standard **malloc()**, its kernel counterpart receives flags specifying the type of memory requested in the second argument. Here, **GFP_KERNEL** means we need a normal kernel memory (not in DMA or high-memory zones) and the function can sleep (reschedule the process) if needed. **sizeof(*buf)** is a common way to get the size of a structure accessible via pointer.
内核内存使用通过**kmalloc()**来分配并**kfree()**来释放内存;**kzalloc()**会将内存设置为全零。不像标准的**malloc()**,它的内核版本接收标志在第二个参数指定内存的类型。这里,**GFP_KERNEL**意味着我们需要一个正常的内核内存(并非直接内存存取或者高内存区)以及函数可以按需休眠(重新安排流程)。**sizeof(*buf)**是一种常用的方式通过指针来获取结构的大小。
你应该经常检查**kmalloc()**的返回值;非空指针将会导致内核崩溃。另外还要注意到**unlikely()**宏的用法。它(与**likely()**宏相反)广泛用于表示内核中判断条件几乎是真(或假)。它不影响流控制,但是有助于现代处理器提高分支预测的性能。
最后,注意**gotos**。他们通常被认为是邪恶的然而在Linux内核中和一些其它系统软件利用他们集中实现函数退出。这将导致更少的深度嵌套和可读代码就更像**try-ctach**块中使用的更高级语言。
有**buffer_alloc()** 和 **buffer_free()**的存在,**open** 和 **close**的方法就回变得非常简单。
static int reverse_open(struct inode *inode, struct file *file)
{
int err = 0;
file->private_data = buffer_alloc(buffer_size);
...
return err;
}
**struct file**是一个标准的内核数据结构,用于存储关于打开的文件信息,例如当前文件位置(**file->f_pos**),标志(**file->f_flags**),或者打开方式(**file->f_mode**).另外一个字段, **file->private_data**常用于关联文件和一些任意数据。它的类型是void *,但是对内核意外的文件拥有者是不透明的。我们将把它存储在缓冲区内。
如果缓存区分配失败,我们通过返回负值显示调用用户空间代码(**-ENOMEM**).C库中利用**open(2)**系统调用(可能是, **glibc**)将会进行检测并适当地设置**errno** 。
#### 学习如何读和写 ####
读和写的方法才是真正工作的完成。当数据在缓冲区被读和写时,我们放弃之前的内容和反向地存储该字段,不需要任何临时存储。读的方法是简单地复制内核缓冲区到用户空间。但是如果还是没有数据在缓冲区时,什么是**reverse_read()**方法应该做的呢?在用户空间,**read()**调用会阻塞,知道数据可用。在内核中,你需要等待。幸运的是,有一个叫做“等待列队”的机制。
这个想法是很简单的。如果一个当前的进程需要等待一些事件,它的描述(a **struct task_struct** 存储为 ‘当前’)被放在非运行状态sleeping并添加到队列。接着**schedule()**会计划选择另一个进程来运行。生成事件的代码通过使用队列把等待事件放进**TASK_RUNNING** 状态来唤醒它们。调度表会选择在未来的某个点选择它们中的一个。Linux有许多非运行进程状态尤其是**TASK_INTERRUPTIBLE**(睡眠状态可以被一个信号中断)和**TASK_KILLABLE**(正在睡眠状态的进程可以被阻断)。所有这些都会被正确把握,而等待队列将会为你安排好这一切。
存储我们的读写队列头的一个天然场所是结构缓存区,所以通过添加**wait_queue_head_t read_queue**字段开始。你将会将**linux/sched.h**包含其中。一个等待队列将会通过DECLARE_WAITQUEUE()宏被静态声明。在我们的例子中,是需要动态初始化的,于是我们添加**buffer_alloc()**这一行:
init_waitqueue_head(&buf->read_queue);
我们等待可用数据;或者**read_ptr != end**条为真时。我们需要等待可终端例如通过Ctrl+C).所以读的方法应该这样开始:
static ssize_t reverse_read(struct file *file, char __user * out,
size_t size, loff_t * off)
{
struct buffer *buf = file->private_data;
ssize_t result;
while (buf->read_ptr == buf->end) {
if (file->f_flags & O_NONBLOCK) {
result = -EAGAIN;
goto out;
}
if (wait_event_interruptible
(buf->read_queue, buf->read_ptr != buf->end)) {
result = -ERESTARTSYS;
goto out;
}
}
...
我们会进行循环,直到有可用数据出现,并且在如果不是的情况下使用**wait_event_interruptible()**(这是一个宏,不是一个函数,这就是为什么队列要通过一个值来传送)来等待。如果是**wait_event_interruptible()**的情况下,很好,会中断,并返回一个非零值,我们将它翻译为**-ERESTARTSYS**.这段代码意味着系统调用将会重启。**file->f_flags**在一个非阻塞模式检查打开的文件数量:如果没有数据,我们会返回**-EAGAIN**.
我们不能使用**if()**来取代**while()**,由于还有许多进程正在等待数据。当**write**方法唤醒他们,调度表会用不可预知的方法来选择一个运行,那么这段代码将在适时有一个机会执行,缓冲区会再次清空。现在我们需要从**buf->data**复制数据到用户空间。**copy_to_user()**内核函数是实现如下:
size = min(size, (size_t) (buf->end - buf->read_ptr));
if (copy_to_user(out, buf->read_ptr, size)) {
result = -EFAULT;
goto out;
}
如果用户空间指针是错误的,这个调用将可能失败;如果发生这种情况,我们会返回**-EFAULT**.切记不要相信任何来自内核意外的东西!
buf->read_ptr += size;
result = size;
out:
return result;
}
为了使数据在任意块可读,简单的算法往往是必要的。这个方法返回了读或者一段错误代码的字节数。
这个写的方法更简单和更简短。首先,我们检查缓冲区是否有足够的空间,接着我们使用**copy_from_userspace()**功能来获取数据。接着**read_ptr**和结束指针重置和缓冲区内容是相反的:
buf->end = buf->data + size;
buf->read_ptr = buf->data;
if (buf->end > buf->data)
reverse_phrase(buf->data, buf->end - 1);
这里,**reverse_phrase()**干了所有重活。这有赖于**reverse_word()**函数,既简短有可以做内联标记。这是另一种常见的优化;然而,你不应该过度使用它,因为内敛使用会使内核镜像变得不必要的大。
最后,我们需要唤醒正在**read_queue**等待数据的进程,正如先前面熟。**wake_up_interruptible()**实现如下:
wake_up_interruptible(&buf->read_queue);
喲!你现在拥有一个至少可以成功调试的内核模块。现在是时候来测试它了。
### 调试内核代码 ###
>也许在内核中大多数常用的调试方法是打印。你可以使用纯**printk()**(大概和**KERN_DEBUG**日志级别相似)如果你愿意。然而,也有更好的方法。使用**pr_debug()** 或者 **dev_dbg()**,如果你正在写一个有“struct device”设备驱动他们支持动态调试(**dyndbg**)特征并可以启用或者禁止请求(请看**Documentation/dynamic-debug-howto.txt**)。对于纯开发的信息,使用**pr_devel()**除非DEBUG被定义这将成为一个空操作。要为我们的模块启用DEBUG包括
>
> CFLAGS_reverse.o := -DDEBUG
>
>在 Makefile中。在这之后使用**dmesg**来查看通过**pr_debug()** 或者 **pr_devel()**生成调试信息。
>或者,你可以通过发送调试信息到控制台。做这一步,你也可以设置**console_loglevel**内核变量为8甚至更高(**echo 8 /proc/sys/kernel/printk**)或者临时在日志级别高的问题上打印调试信息例如**KERN_ERR**.自然而然,你将会在你发布代码之前删除这类调试语句。
>注意内核在控制台出现的内核信息并不是像Xterm的一个在终端模拟窗口这就是为什么你会发现建议不要在X环境下开发内核。
### 惊喜,惊喜! ###
编译模块并加载大内核:
$ make
$ sudo insmod reverse.ko buffer_size=2048
$ lsmod
reverse 2419 0
$ ls -l /dev/reverse
crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse
一切似乎都就绪了。现在,来测试模块如何工作,我们将要写一段小程序来反转第一个命令行参数。在**main()**函数(sans error checking)你可以看到如下:
int fd = open("/dev/reverse", O_RDWR);
write(fd, argv[1], strlen(argv[1]));
read(fd, argv[1], strlen(argv[1]));
printf("Read: %s\n", argv[1]);
像这样运行:
$ ./test 'A quick brown fox jumped over the lazy dog'
Read: dog lazy the over jumped fox brown quick A
它正常工作了!玩得跟尽兴一点:尝试传递一个单词或者一个字母的字符串,空的或者是非英语字符串(如果你有一个键盘布局设置)以及其它人和东西。
现在让我们把事情变得更棘手一点。我们将要创建2个进程用于共享文件描述因此还有内核缓冲区。一个将持续写字符串到设备中去而另一个回去读取它们。**fork(2)**系统笤俑在下面例子中用到,但是线程还是会很好地工作。我也省略了开关设备的代码和错误检查(再次提到):
char *phrase = "A quick brown fox jumped over the lazy dog";
if (fork())
/* Parent is the writer */
while (1)
write(fd, phrase, len);
else
/* child is the reader */
while (1) {
read(fd, buf, len);
printf("Read: %s\n", buf);
}
你希望这段程序输出什么呢?下面是我从我的笔记本电脑上获得的:
Read: dog lazy the over jumped fox brown quick A
Read: A kcicq brown fox jumped over the lazy dog
Read: A kciuq nworb xor jumped fox brown quick A
Read: A kciuq nworb xor jumped fox brown quick A
...
这里接下来会发生什么?这是一场比赛。我们认为**read**和**write**是原子,或者从一开始到结束执行一个指令。然而内核是一个并发的猛兽,它可以很轻易地重新安排在内核**write**操作部分运行的进程,这个进程在**reverse_phrase()**函数中。如果进程是在作者有机会完成之前,**read()**是按计划执行的,它将会看到数据处于不一致的状态。这些错误真的很难调试出来。那么要如何修复它呢?
基本上,我们需要确保没有**read**方法可以被执行知道写方法有返回。如果你曾经设定一个多线程应用你可能已经看到原始同步像互斥锁和信号。Linux也同样有这些但是存在细微差别。内核代码可以运行进程上下文用户空间代码的“代表”工作就像我们使用的方法和终端上下文例如一个IRQ处理线程。如果你已经在进程上下文中和你所需要的锁你只需要简单地睡眠和重试直到成功位置。在终端上下文时你不能处于休眠状态因此代码会在一个循环中运行直到锁是可使用。相应的原子成为自旋锁但在我们的例子中是一个简单的互斥 —— 这个对象只有一个进程可以在特定的时间“hold”住 —— 这就足够了。根据性能因素,在现实的代码中也可以使用读写信号。
锁通常用来保护一些数据(在我们的例子中,一个“就够缓冲区”实例),是一种非常常见的嵌入结构。所以我们结构缓冲区中添加一个互斥锁(‘结构互斥锁’).我们必须初始化使用**mutex_init()**对锁进行初始化;**buffer_alloc()**恰好用来处理这个。这段代码使用锁也必须包含**linux/mutex.h**.
一个锁就像一个交通灯 ——除非司机看它和遵守信号否则将起不了作用。所以我们需要升级**reverse_read()** 和 **reverse_write()** 来确保在对缓冲区做任何事情或者释放时完成获得互斥锁。让我们来看一看**read**方法 —— **write**也是用同样的方式工作的:
static ssize_t reverse_read(struct file *file, char __user * out,
size_t size, loff_t * off)
{
struct buffer *buf = file->private_data;
ssize_t result;
if (mutex_lock_interruptible(&buf->lock)) {
result = -ERESTARTSYS;
goto out;
}
我们需要在每个函数的开始有锁。**mutex_lock_interruptible()**也可以获取锁或者将进程返回至休眠状态,直到锁是可利用。正如之前,**_interruptible**后缀意味着休眠状态也可以被一个信号阻断。
while (buf->read_ptr == buf->end) {
mutex_unlock(&buf->lock);
/* ... wait_event_interruptible() here ... */
if (mutex_lock_interruptible(&buf->lock)) {
result = -ERESTARTSYS;
goto out;
}
}
下面是”我们等待数据“的循环。当有互斥锁时,你不应该处于休眠状态,或可能发生一种情况叫”死锁“。所以,如果没有数据,我们要释放锁并调用**wait_event_interruptible()**.当它返回时,我们需要锁并继续和往常一样:
if (copy_to_user(out, buf->read_ptr, size)) {
result = -EFAULT;
goto out_unlock;
}
...
out_unlock:
mutex_unlock(&buf->lock);
out:
return result;
最后,当函数结束时互斥对象已经被解锁了或者当一个错误发生时互斥锁被持有。重新编译模块(不要忘记重新加载它)并再次运行第二个测试。现在你应该看到没有损坏的数据了。
### 接下来做什么呢? ###
现在你已经尝试了做内核黑客了。我们只是触及表面的话题还有更多呢。我们的第一个模块是特意设成简单的然而你学到的概念将在更复杂的场景中保持不变。并发性、方法表、注册回调将进程设置成睡眠状态和唤醒它们是每个内核黑客需要使用的现在你已经看到它们所有都在行动啦。也许总有一天内核代码会在主线Linux代码树上 —— 如果这一天到来,请联系我们!
--------------------------------------------------------------------------------
via: http://www.linuxvoice.com/be-a-kernel-hacker/
译者disylee(https://github.com/译者ID) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://www.libusb.org/
[2]:http://fuse.sf.net/
[3]:http://www.linuxvoice.com/be-a-kernel-hacker/www.netfilter.org/projects/libnetfilter_queue
[4]:http://lxr.free-electrons.com/

View File

@ -0,0 +1,138 @@
如何在Ubuntu,Linux Mint,Debian上禁用Ipv6
================================================================================
### Ipv6 ###
IPv6是寻址方案Ipv4的下一个版本,被用来给如google.com这样的域名分配数字地址。
Ipv6比Ipv4支持更多的地址。然而它还没有被广泛支持还在被接受的过程中。
### 你的系统支持Ipv6么? ###
为了支持Ipv6需要很多事情。首先你需要系统/操作系统支持Ipv6。UbuntuLinux Mint和大多是现代发行版都支持它。如果你看一下ifconfig指令的输出你就会看见你的网络接口被分配了ipv6地址。
$ ifconfig
eth0 Link encap:Ethernet HWaddr 00:1c:c0:f8:79:ee
inet addr:192.168.1.2 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::21c:c0ff:fef8:79ee/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:110880 errors:0 dropped:0 overruns:0 frame:0
TX packets:111960 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:62289395 (62.2 MB) TX bytes:25169458 (25.1 MB)
Interrupt:20 Memory:e3200000-e3220000
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:45258 errors:0 dropped:0 overruns:0 frame:0
TX packets:45258 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4900560 (4.9 MB) TX bytes:4900560 (4.9 MB)
看一下行“inet6 addr”。
接下来你需要一个支持ipv6的路由器/调制解调器。额外地你的ISP也必须支持ipv6。
除了检查网络设备的每一部分最好查出你是否可以通过ipv6访问网站。
有很多网站可以检测你的连接是否支持ipv6. 这里就是个例子:[http://testmyipv6.com/][1]
下面是在内核中启用ipv6的参数
$ sysctl net.ipv6.conf.all.disable_ipv6
net.ipv6.conf.all.disable_ipv6 = 0
$ sysctl net.ipv6.conf.default.disable_ipv6
net.ipv6.conf.default.disable_ipv6 = 0
$ sysctl net.ipv6.conf.lo.disable_ipv6
net.ipv6.conf.lo.disable_ipv6 = 0
同样可以在proc文件中检查
$ cat /proc/sys/net/ipv6/conf/all/disable_ipv6
0
注意这里的变量是控制ipv6的“禁用”。所以设置1就会禁用ipv6。
### 如果它不支持就禁用ipv6 ###
如果你的网络设备中不支持ipv6那最好就全部禁用它们。为什么因为这回引起延迟域查询在网络连接中不必要地尝试连接到ipv6地址导致延迟等等问题。
我也遇到过像这样的问题apt-get命令偶尔会尝试连接到ipv6地址失败接着检索ipv4地址。看一下下面的输出。
$ sudo apt-get update
Ign http://archive.canonical.com trusty InRelease
Ign http://archive.canonical.com raring InRelease
Err http://archive.canonical.com trusty Release.gpg
Cannot initiate the connection to archive.canonical.com:80 (2001:67c:1360:8c01::1b). - connect (101: Network is unreachable) [IP: 2001:67c:1360:8c01::1b 80]
Err http://archive.canonical.com raring Release.gpg
Cannot initiate the connection to archive.canonical.com:80 (2001:67c:1360:8c01::1b). - connect (101: Network is unreachable) [IP: 2001:67c:1360:8c01::1b 80]
.....
像这样的错误在最近的Ubuntu中更频繁了或许它比以前更频繁地尝试使用IPv6地址。
我在其他的应用上也注意到了相似的问题如Hexchat同样Google Chrome也会有时会在查询域名的时候花费更长的时间。
所以最好的方案是完全禁用Ipv6来摆脱这些事情。这只需要一点点配置但可以帮助你解决很多你系统上的很多问题。用户甚至反应这可以加速网络。
#### 禁用 Ipv6 - 方案1 ####
编辑文件 - /etc/sysctl.conf
$ sudo gedit /etc/sysctl.conf
在文件的最后加入下面的行。
# IPv6 disabled
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
保存并关闭
重启sysctl
$ sudo sysctl -p
再次检查ifconfig的输出这里应该没有ipv6地址了。
$ ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:5f:28:8b
inet addr:192.168.1.3 Bcast:192.168.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1346 errors:0 dropped:0 overruns:0 frame:0
TX packets:965 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1501691 (1.5 MB) TX bytes:104883 (104.8 KB)
If it does not work, then try rebooting the system and check ifconfig again.
如果不行尝试重启系统并再次检查ifconfig
#### 禁用 ipv6 - GRUB 方案 ####
Ipv6同样可以通过编辑grub配置文件禁用。
$ sudo gedit /etc/default/grub
查找包含"GRUB_CMDLINE_LINUX"的行,并如下编辑:
GRUB_CMDLINE_LINUX="ipv6.disable=1"
同样可以加入名为"GRUB_CMDLINE_LINUX_DEFAULT"的变量这同样有用。保存并关闭文件重新生成grub配置。
$ sudo update-grub2
重启现在ipv应该就已经禁用了。
--------------------------------------------------------------------------------
via: http://www.binarytides.com/disable-ipv6-ubuntu/
译者:[geekpi](https://github.com/geekpi) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](http://linux.cn/) 荣誉推出
[1]:http://testmyipv6.com/