为了方便后续的深入,我们在驱动程序中用printk( )函数来打印“hello world”,printk( )是内核中自带的函数,专门用于在打印内核信息。

在安装驱动模块到内核中的时,需要进行驱动模块的初始化,初始化具体做什么我们先不提,我们暂时只用printk( )打印“hello world”:

int first_drv_init(void)

{   

    printk("hello world!\n");

    return 0;

}

· 关于printk的打印级别

在内核中有一个打印级别的概念:

当我们在linux命令行下输入:cat /proc/sys/kernel/printk时,会打印出:4       4       1       7

第一个“4”表示当前系统允许打印级别高于4的字符串显示在命令行中,也就是打印级别为3、2、1、0的都能显示出来,7、6、5、4打印级别的都不能被显示出来,一共有0~7这8个打印级别,宏定义如下所示:

#define KERN_EMERG        "<0>" /* 1116.www.qixoo.qixoo.com system is unusable */
#define KERN_ALERT         "<1>" /* action must be taken immediately */
#define KERN_CRIT            "<2>" /* critical conditions */
#define KERN_ERR             "<3>" /* error conditions */
#define KERN_WARNING    "<4>" /* warning conditions */
#define KERN_NOTICE        "<5>" /* normal but significant condition */
#define KERN_INFO            "<6>" /* informational */
#define KERN_DEBUG         "<7>" /* debug-level messages */

我们可以在打印函数printk中这样赋予字符串打印级别: printk(KERN_INFO"hello world!\n");

这样"hello world"的打印级别是6,我们可以用vi命令打开/proc/sys/kernel/printk,把第一个4修改成7,这样KERN_INFO所修饰的"hello world"就能够被打印出来。

· static

在int first_drv_init(void)这个函数前,一般习惯加上static关键字进行修饰:static int first_drv_init(void);这样将使得int first_drv_init(void)函数只对能在first_drv.c文件中被使用,即为first_drv.c的私有函数,外部的文件中的代码无法对first_drv.c中的int first_drv_init(void)进行访问,这样做的好处是,可以防止函数重名带来不必要的麻烦,如果函数重名且未加static修饰,编译的时候可能就会出错误或警告,即便程序能运行可能也会出现莫名其妙的问题。

· __init

另外,除了用static关键字来修饰函数外,最好再养成习惯在函数前再加上"__init",这是一个宏定义,在编译器对驱动程序进行编译的时候,就会将__init所修饰的函数放到.init.text段中,不加__init将默认把函数放到.text段中,各种驱动模块凡是由__init修饰的函数将会被统一放到.init.text段,内核启动时会统一加载.init.text段中的这些驱动模块初始化函数,加载完后就会把这个.init.text段释放掉以省出内存。

经过这番分析后,我们第一个驱动模块的初始化函数就变成了:

static int __init first_drv_init(void)

{   

    printk(KERN_INFO"hello world!\n");

    return 0;

}

其实我们经常听说安装驱动,也听说过卸载驱动,那么一个完整的驱动模块也应该是可以被卸载的,我们可以照之前的方法来写一个卸载函数:

static int __exit first_drv_exit(void)

{   

    printk(KERN_INFO"goodbye world...\n");

    return 0;

}

· __exit

__exit宏定义的功能和作用与__init宏定义如出一辙,即将函数放入到.exit.text段,在此不多赘述。

此时我们发现,first_drv.c里面空荡荡的,只有first_drv_init和first_drv_exit两个函数,连个main函数都没有,谁来调用这两个函数呢,总不能让编译器指定先执行first_drv_init,再指定执行first_drv_exit吧,这思路显然是错的,只是我们的一厢情愿。

实际上,在first_drv.c中,我们至始至终都不需要main函数,不论多么复杂、高级的驱动模块都不需要在其"驱动.c"文件中写一个main函数,这是为什么呢?在下一篇博文后,你可能会有更深的体会,现在你只需要明白驱动模块中的函数只是被别的函数或应用程序来进行调用的罢了。

那么被谁调用呢,驱动模块的初始化函数由module_init( )这个宏定义来进行处理,module_init( )定义了一个结构体,当我们写成module_init(first_drv_init)时,那个结构体中就会有一个函数指针指向了first_drv_init函数,当我们在命令行下使用insmod命令来安装first_drv这个驱动模块的时候,内核就会自动去找到那个结构体,通过那个函数指针找到first_drv_init函数进行调用。

对应的,内核也实现了module_exit( )宏定义来让我们实现对first_drv_exit函数的调用,用法为module_exit(first_drv_exit)

由于使用到了module_init( )和module_exit( )这两个宏定义,我们需要添加linux/module.h这个头文件,我们之前也使用了__init和__exit这两个宏定义,同样需要加上头文件linux/init.h

我们的第一个空壳驱动程序就完成了,为什么说是空壳呢,因为这个驱动程序仅仅能够被安装和卸载,不涉及硬件操作,更为重要的是,它也无法被应用程序调用,它还不具备和应用层之间的接口,我们知道你脑中一片乱麻,我将在下一篇博文中讲解整个系统各层次之间的简单关系,到时你的思路就会清晰很多。

下面附上这篇博文中我们写的完整代码:

#include "linux/module.h" //此处没有使用<>而是使用了"",是因为博客编辑的<>之间的内容将不予显示

#include "linux/init.h" //<>还是""的区别不大,不过还是建议大家用<>

static int __init first_drv_init(void)

{   

    printk(KERN_INFO"hello world!\n");

    return 0;

}

static int __exit first_drv_exit(void)

{   

    printk(KERN_INFO"goodbye world...\n");

    return 0;

}

module_init(first_drv_init);

module_exit(first_drv_exit);

它还有一个不足之处,我们将在下下篇博文中进行完善,然后进行测试,大家可以先把它敲到你的first_drv.c中,不要复制,最好是跟着这篇博文的思路从头到尾敲,一步步完善成上面这样。

第1个linux驱动___打印"hello world"的更多相关文章

  1. Linux驱动学习之常用的模块操作命令

    1.常用的模块操作命令 (1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 (2)insmod(install module,安装模块),功能是向当前 ...

  2. 嵌入式Linux驱动开发日记

    嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...

  3. Linux驱动学习步骤(转载)

    1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ls ...

  4. Linux驱动设计—— 驱动调试技术

    参考博客与书籍: <Linux设备驱动开发详解> <Linux设备驱动程序> http://blog.chinaunix.net/uid-24219701-id-2884942 ...

  5. Linux驱动开发之开篇--HelloWorld

    Linux驱动的编写,大致分为两个过程,第一个过程为测试阶段,即为某一具体的设备,添加必要的驱动模块,为了节省编译时间,需要将代码单独放在一处,在编译时,只需要要调用内核的头文件即可:第二个过程为布置 ...

  6. Linux驱动开发 -- 打开dev_dbg()

    Linux驱动开发 -- 打开dev_dbg() -- :: 分类: LINUX linux设备驱动调试,我们在内核中看到内核使用dev_dbg来控制输出信息,这个函数的实质是调用printk(KER ...

  7. Linux驱动开发学习的一些必要步骤

      1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...

  8. Android系统移植与驱动开发——第六章——使用实例来理解Linux驱动开发及心得

    Linux驱动的工作方式就是交互.例如向Linux打印机驱动发送一个打印命令,可以直接使用C语言函数open打开设备文件,在使用C语言函数ioctl向该驱动的设备文件发送打印命令.编写Linux驱动最 ...

  9. linux驱动调试--段错误之oops信息分析

    linux驱动调试--段错误之oops信息分析 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id= ...

随机推荐

  1. UWP 快速的Master/Detail实现

    最近在写快报(还没有写完)的过程中,一开始就遇到了这个Master/Detail如何实现的问题. 微软给出Demo并不符合要求,搜索后找到了今日头条开发者写的一篇 :实现Master/Detail布局 ...

  2. sencha xtype清单

    xtype Class ----------------- --------------------- actionsheet Ext.ActionSheet audio Ext.Audio butt ...

  3. php缓存技术(减少数据库服务器压力)

    静态缓存(保存在磁盘上的静态文件,用PHP生成数据放入静态文件中) a)  php操作缓存 i.  生成缓存 ii.  获取缓存 iii. 删除缓存 判断目录是否存在:is_dir() dirname ...

  4. nios II--实验2——led软件部分

    软件开发 首先,在硬件工程文件夹里面新建一个software的文件夹用于放置软件部分:打开toolsàNios II 11.0 Software Build Tools for Eclipse,需要进 ...

  5. [HDOJ5451]Best Solver(乱搞)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=5451 分析:A=5+2根号6 B=6-2根号6 n=1+2^x 那么A^n+B^n是整数 注意到0< ...

  6. javascript 函数声明与函数表达式的区别

    先看一段代码 var f = function g() { return 1; }; if (false) { f = function g(){ return 2; }; } alert(g()); ...

  7. less 学习 (计划终于执行了啊,不再拖延了)

    1.less是什么? 答:将CSS赋予动态语言的特性,   变量,继承,运算,函数. (less就是一个用js实现的CSS解析器,运行要依赖js引擎). 2.运行原理: 按照指定语法规则写好less文 ...

  8. 如何在iOS地图上高效的显示大量数据

    2016-01-13 / 23:02:13 刚才在微信上看到这篇由cocoachina翻译小组成员翻译的文章,觉得还是挺值得参考的,因此转载至此,原文请移步:http://robots.thought ...

  9. 使用 Spring 3 来创建 RESTful Web Services

    来源于:https://www.ibm.com/developerworks/cn/web/wa-spring3webserv/ 在 Java™ 中,您可以使用以下几种方法来创建 RESTful We ...

  10. java中的hashSet和Treeset的分析

    hashset中的元素 treeset中的元素要实现comparable接口