精华内容
下载资源
问答
  • Linux内核驱动工程师是做什么的?如何成为一个优秀的驱动开发工程师?应该具备什么样的能力?从简到难,IT培训网给大家解析相关的问题。Linux内核驱动工程师是做什么的?能够独立完成驱动的功能开发任务能够分析和优化...

    Linux内核驱动工程师是做什么的?如何成为一个优秀的驱动开发工程师?应该具备什么样的能力?从简到难,IT培训网给大家解析相关的问题。

    Linux内核驱动工程师是做什么的?

    能够独立完成驱动的功能开发任务

    能够分析和优化驱动的性能,针对特定硬件扬长避短

    能够充分了解模块相关软硬件能力、发展方向,辅助应用工程师最大化利用硬件能力

    能够辅助硬件工程师规划硬件设计,预防问题,谋求功能模块的最佳方案

    能够协助定义系统架构,合理规划软硬件,谋求产品实现的最佳方案

    7f58ad76a7070d23cedc580b6acf50ed.png

    Linux驱动工程师具备哪些能力?

    一、足够的硬件知识

    能看简单的原理图,能够分析硬件异常的可能原因,能够使用常见的硬件调试工具,我想这是做为优秀的驱动工程师,区别与其它软件工程师,所不可避免、必须具备的专业素质。当然取决于你具体从事的工作,对这方面的要求不尽相同。对于驱动开发者来说,不了解所开发驱动外设的硬件原理和相关背景知识,也许很多时候,也能够完成一些移植,修补的工作任务,但这就好比无源之水,无根之木,我相信是很难走远的。

    二、多多益善的操作系统知识

    做驱动开发,特别是纯粹的外设的驱动移植工作,刚开始的时候,也许你并不需要了解很多操作系统本身的知识(像内存管理,进程调度,锁,各种内核子系统的原理框架等等),也能顺利完成手头的一些工作。但是,如果一但需要优化驱动,需要完善软件框架,或者是遇上疑难问题需要跟踪解决,对操作系统,内核本身的了解,就体现出它的价值了。

    对于Linux内核驱动开发者,尤其如此,首先,代码是完全开源的,你有条件去了解背后的运行机制,其次,Linux内核和各个组成子系统总是在迅速的进化发展中,不进则退,你也有必要跟上时代发展的脚步。

    三、强烈的好奇心,持续的热情

    如果驱动开发不仅仅是你的爱好,更是你养家糊口的途径,很多时候,你大概不会有机会专注于一两个你最有经验的模块的开发和维护。随着能力的成长,势必会要求你接触和掌握越来越多的各式各样的驱动模块的开发。最起码的标准应该是对具体驱动模块相关的子系统的整体工作流程,框架,具备足够的好奇心,乐于去了解和学习,而不仅仅是为了完成任务而工作,否则的话,很难积累下扎实的经验和技术。

    四、清晰的逻辑思维能力

    这一点,也许是个软件开发人员都应该具备吧,不过,做为驱动开发工程师来说,有时候,大多数情况下,工作的硬件环境并不是完美的,遇到问题需要分析判断错误的原因是硬件问题还是驱动Bug,这时候,清晰的逻辑思维能力尤其重要。

    五、良好的工作习惯

    大多数人都不是天才,要成为优秀的开发工程师,其一需要持续努力,其二需要时间积累经验,而这过程中,很重要的一点,就是要有良好的工作习惯。譬如,注意设计文档的维护,对工作中遇到的问题的记录,过往经验的及时记录,适当的软件开发流程等等。文档工作,可能很多人很不愿意去做,它的确很花费时间。不过,好记性不如烂笔头啊 。当然,其实设计文档更多的是为你提供思考的机会,而过往经验的总结,也可以起到和大家交流技术,共同进步的目的。

    六、英语

    这个也是必须的啦,没有办法,邮件列表,技术文档,社区,精通英语肯定是很大的优势,做开源项目尤其如此。阅读各种Spec标准文档之类的速度还是很重要的。阅读无障碍是一回事,能和母语一样一目十行,人生苦短,效率啊!

    想成为一名优秀的Linux内核驱动工程师,对号入座,看看你是否具备以上条件,或具备其中的几点呢?

    展开全文
  • 浏览器的内核 ...Opera(欧朋浏览器):以前 presto 内核, Opera 现已改用 Google Chrome 的 Blink 内核 -o- Chrome(谷歌浏览器):Blink(基于 webkit , Google 与 Opera Software 共同开发 ) -w

    浏览器的内核

    • IE(IE浏览器) : trident 内核 -ms-
    • Firefox(火狐浏览器) : gecko 内核 -moz-
    • Safari(苹果浏览器) :webkit 内核 -webkit-
    • Opera(欧朋浏览器):以前是 presto 内核, Opera 现已改用 Google Chrome 的 Blink 内核 -o-
    • Chrome(谷歌浏览器):Blink(基于 webkit , Google 与 Opera Software 共同开发 ) -webkit-

    什么是内核?

    浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“渲染引擎”,不过我们一般习惯将之称为“浏览器内核”。负责对网页语法的解释(如标准通用标记语言下的一个应用HTML、JavaScript)并渲染(显示)网页。 所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。

    内核分类

    • Trident(IE内核):该内核程序在1997年的IE4中首次被采用。Trident实际上是一款开放的内核,其接口内核设计的相当成熟,因此才有许多采用IE内核而非IE的浏览器(壳浏览器)涌现。
      由于IE本身的“垄断性”(虽然名义上IE并非垄断,但实际上,特别是从Windows 95年代一直到XP初期,就市场占有率来说IE的确借助Windows的东风处于“垄断”的地位)而使得Trident内核的长期一家独大,微软很长时间都并没有更新Trident内核,这导致了两个后果——一是Trident内核曾经几乎与W3C标准脱节(2005年),二是Trident内核的大量 Bug等安全性问题没有得到及时解决,然后加上一些致力于开源的开发者和一些学者们公开自己认为IE浏览器不安全的观点,也有很多用户转向了其他浏览器,Firefox和Opera就是这个时候兴起的。非Trident内核浏览器的市场占有率大幅提高也致使许多网页开发人员开始注意网页标准和非IE浏览器的浏览效果问题。
    • Gecko(Firefox内核):Netscape6开始采用的内核,后来的Mozilla FireFox(火狐浏览器) 也采用了该内核,Gecko的特点是代码完全公开,因此,其可开发程度很高,全世界的程序员都可以为其编写代码,增加功能。因为这是个开源内核,因此受到许多人的青睐,Gecko内核的浏览器也很多,这也是Gecko内核虽然年轻但市场占有率能够迅速提高的重要原因。
      事实上,Gecko引擎的由来跟IE不无关系,前面说过IE没有使用W3C的标准,这导致了微软内部一些开发人员的不满;他们与当时已经停止更新了的 Netscape的一些员工一起创办了Mozilla,以当时的Mosaic内核为基础重新编写内核,于是开发出了Gecko。不过事实上,Gecko 内核的浏览器仍然还是Firefox (火狐) 用户最多,所以有时也会被称为Firefox内核。此外Gecko也是一个跨平台内核,可以在Windows、 BSD、Linux和Mac OS X中使用。
    • Presto(Opera前内核) (已废弃): Opera12.17及更早版本曾经采用的内核,现已停止开发并废弃,该内核在2003年的Opera7中首次被使用,该款引擎的特点就是渲染速度的优化达到了极致,然而代价是牺牲了网页的兼容性。
      使用Presto的除开Opera以外,只剩下NDSBrowser、Wii Internet Channle、Nokia 770网络浏览器等,这很大程度上限制了Presto的发展。
      Opera现已改用Google Chrome的Blink内核。
    • Webkit(Safari内核,Chrome内核原型,开源):它是苹果公司自己的内核,也是苹果的Safari浏览器使用的内核。 Webkit引擎包含WebCore排版引擎及JavaScriptCore解析引擎,均是从KDE的KHTML及KJS引擎衍生而来,它们都是自由软件,在GPL条约下授权,同时支持BSD系统的开发。所以Webkit也是自由软件,同时开放源代码。在安全方面不受IE、Firefox的制约,所以Safari浏览器在国内还是很安全的。
      限于Mac OS X的使用不广泛和Safari浏览器曾经只是Mac OS X的专属浏览器,这个内核本身应该说市场范围并不大;但似乎根据最新的浏览器调查表明,该浏览器的市场甚至已经超过了Opera的Presto了——当然这一方面得益于苹果转到x86架构之后的人气暴涨,另外也是因为Safari 3终于推出了Windows版的缘故吧。Mac下还有OmniWeb、Shiira等人气很高的浏览器。
      Google Chrome、360极速浏览器以及搜狗高速浏览器高速模式也使用Webkit作为内核(在脚本理解方面,Chrome使用自己研发的V8引擎)。WebKit 内核在手机上的应用也十分广泛,例如 Google 的手机 Gphone、 Apple 的iPhone, Nokia’s Series 60 browser 等所使用的 Browser 内核引擎,都是基于 WebKit。
    展开全文
  • IE: trident内核 Firefox:gecko内核 Safari:webkit内核 Opera:以前presto内核,Opera现已改用Google - Chrome的Blink内核 Chrome:Blink(基于webkit,Google与Opera Software共同开发)
    • IE: trident内核
    • Firefoxgecko内核
    • Safari:webkit内核
    • Opera:以前是presto内核,Opera现已改用Google - ChromeBlink内核
    • Chrome:Blink(基于webkitGoogleOpera Software共同开发)
    展开全文
  • linux内核开发

    千次阅读 2021-01-28 14:42:51
    浮点运算起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,因此你需要想想并发会导致什么。而除了所有这一切之外,Linux内核只是一个很大的、很复杂的C程序,它对每个人开放,任何人都去读它、学习它并改进...

    内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了。Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统。浮点运算做起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,因此你需要想想并发会导致什么。而除了所有这一切之外,Linux内核只是一个很大的、很复杂的C程序,它对每个人开放,任何人都去读它、学习它并改进它,而你也可以是其中之一。

    img

    学习内核编程的最简单的方式也许就是写个内核模块:一段可以动态加载进内核的代码。模块所能做的事是有限的——例如,他们不能在类似进程描述符这样的公共数据结构中增减字段(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
    • 你要开发一个文件系统 —— 试试FUSE
    • 你在扩展Netfilter —— 那么libnetfilter_queue对你有所帮助。

    通常,内核里面代码的性能会更好,但是对于许多项目而言,这点性能丢失并不严重。

    由于内核编程总是异步的,没有一个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吧。它是一个基于web的内核导航工具,即使它的功能没有Cscope来得多(例如,你不能方便地找到函数的用法),但它仍然提供了足够多的快速查询功能。

    现在是时候来编译模块了。你需要你正在运行的内核版本头文件(linux-headers,或者等同的软件包)和build-essential(或者类似的包)。接下来,该创建一个标准的Makefile模板:

    obj-m += reverse.oall:    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:    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_ptrread()开始读取数据的地方。缓冲区的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()openclose方法就变得很简单了。

    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_headt 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$ lsmodreverse 2419 0$ ls -l /dev/reversecrw-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 ARead: A kcicq brown fox jumped over the lazy dogRead: A kciuq nworb xor jumped fox brown quick ARead: A kciuq nworb xor jumped fox brown quick A...

    这里发生了什么呢?就像举行了一场比赛。我们认为readwrite是原子操作,或者从头到尾一次执行一个指令。然而,内核确实无序并发的,随便就重新调度了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 disylee 校对:wxy

    本文由 LCTT 原创翻译,Linux中国 荣誉推出

    以上就是良许教程网为各位朋友分享的Linux相关知识。

    展开全文
  • 鸿蒙内核开发概述

    千次阅读 2020-09-18 15:00:51
    BIOS,它做什么?一些自检,然后从硬盘上读入windows,并启动它。类似的,这个BIOS对应于鸿蒙里的bootloader。Bootloader的作用就是去Flash、SD卡等设备上读入鸿蒙内核,并启动它。②Windows系统必需的软件,比如...
  • 01_内核开发基础

    2020-02-04 21:01:41
    01_内核开发基础 1.1Linux体系结构 Linux体系结构由用户空间和内核空间构成,为什么Linux体系要分为用户空间和内核空间? •从程序员的角度分析 –将linux底层和应用分开,应用的应用,底层的底层...
  • 对于前端开发工作人员来说,不管在实际的开发中,还是在面试中 ,我们不得不需要了解知道市面上的浏览器类型及浏览器所使用的内核,因为只有知道了这些知识,我们才会知道怎么才能正确的浏览器兼容。 市面上分...
  • (1) Android版本有哪些分支可用?每个分支的TAG是什么?...Android对标准的Linux内核做了哪些修改?   (3) Android源码分支与Linux版本分支的对应关系是什么样的?高版本的Android源码能否使用低版本的Lin
  • 变量命名法 这里linux不是windows,所以匈牙利命名法不允许使用的,在内核中,局部变量只要可以明确表达自己的意思...函数的注释应该表达这个函数什么,为什么做,而不是像以前一样写一大段这个程序怎么写的...
  • 一、linux内核驱动开发基础 .Linux体系结构 .Linux内核结构 .Linux内核源代码目录结构 Linux体系结构 .从上图可知,Linux体系结构由用户空间和内核空间构成 .为什么Linux体系要分为用户空间和内核空间? .从程序员...
  • 最近在手机浏览器的开发,发现微信内嵌的浏览器很奇怪,以为webkit内核的,但是webkit兼容的JS它不支持,html5也不支持,如果不是回是什么内核那,而且同样的iphone5在微信浏览器里看到的效果都一样,会出现闪断...
  • 工欲善其事,必先利其器,作为我的《Android内核开发》系列的第一篇文章,我们先来解决一个非技术问题:学习Android内核开发,是否需要购买一块Android开发板呢?如果需要,那么应该选购什么样的开发板呢?1. 是否...
  • Linux内核开发_2_Initramf

    2020-05-13 18:16:54
    在最初,Linus(Linux内核的作者)早期想要编写一个能够与chech(CPU缓存)的文件系统,Linus在rasmfs(ram(与内存交互的文件系统)上的chech(缓存功能)上了封装,使其能够对主板上的L1和L2等不同级别的缓存进行交互,.....
  • 以前在windows的GUI编程中,让我印象最...Timer功能实在太重要了,如果没有定时器,操作系统很多任务都不了,至少你编程画个时钟,搞个闹钟程序什么的,你就没法实现。从这节开始,我们看看timer功能怎么实现的。
  • Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核技术,从来没有终南捷径,拼的就是坐冷板凳的傻劲。 这一个连阅读都被碎片化的时代,在...
  • 嵌入式开发是在操作系统内部开发的,操作系统所有的内核几乎都是C语言编写,如果我们也使用C语言开发,必定会具备一定的优势。 再者,C语言相较其他的高级编程语言,其所有的特点对于嵌入式开发软件是再合适...
  • 曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引...浮点运算起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,因此你需要想想并发会导致什么。而除了所有这一切之外,L...
  • 2) Linux中的用户模式和内核模式是什么含意?3) 怎样申请大块内核内存?4) 用户进程间通信主要哪几种方式?5) 通过伙伴系统申请内核内存的函数有哪些?6) 通过slab分配器申请内核内存的函数有?7) 
  • 浮点运算起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,因此你需要想想并发会导致什么。而除了所有这一切之外,Linux内核只是一个很大的、很复杂的C程序,它对每个人开放,任何人都去读它、学习它并改进...
  • 之前有一段时间,感觉阿里巴巴的内核维护开发团队很牛,最近一直没听到什么声音,官网也好久没用更新了。那么阿里巴巴的内核维护开发团队到底怎么了?...有知道国内内核开发现状的朋友,欢迎留言,共同交流,...
  • 从单片机开发到linux内核驱动

    千次阅读 2019-01-25 21:48:29
    内核驱动其实和开发单片机没什么两样。这里拿高通的一款路由器芯片QCA4531与常见的单片机STM32对比。前者通常跑嵌入式linux系统,后者通常跑裸机或者简单的实时操作系统。 那么用这两款芯片分别实现控制一个...
  • Linux内核的Oops

    千次阅读 2011-09-27 10:39:31
    什么是Oops?从语言学的角度说,Oops应该一个拟声词。当出了点小事故,或者了比较尴尬的事之后,你可以说"Oops",翻译成中国话就叫做“哎呦”。“哎呦,对不起,对不起,我真...在Linux内核开发中的Oops是什么
  • 然而今天的项目进度完了,看看还有写时间,想着也别浪费么,那就简单写一些,就当练习一下自己写文章的能力了吧!正文开始:数据类型这个东西吧,但凡高级语言,肯定离不开的,这样一来,这个东西其实就是...
  • 了解嵌入式开发的朋友们都非常的...首先来说这应该是必然的吧,嵌入式开发是在操作系统内部开发的,而操作系统所有的内核都是C语言所编写的,所以说在嵌入式开发的过程中也选择C语言,肯定是具有一定的优势的。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 954
精华内容 381
关键字:

内核开发是做什么的