为了方便后续的深入,我们在驱动程序中用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. python动态网页爬取——四六级成绩批量爬取

    需求: 四六级成绩查询网站我所知道的有两个:学信网(http://www.chsi.com.cn/cet/)和99宿舍(http://cet.99sushe.com/),这两个网站采用的都是动态网页. ...

  2. centos7 安装nginx和php5.6.25遇到 无法访问php页面 报错file not found 问题解决

    php-fpm安装完成,nginx安装完成 netstap -ntl| 发下端口正常开启 iptables -L 返现9000端口已经开放 ps -aux|grep nginx 发下nginx进程正常 ...

  3. 无法将分支 master 发布到远程 origin,因为远程存储库中已存在具有同一名称的分支

    无法将分支 master 发布到远程 origin,因为远程存储库中已存在具有同一名称的分支.发布此分支将导致远程存储库中的分支发生非快进更新. 第一次用oschina的git设置完远程仓库后提交出现 ...

  4. Webpack配置示例和详细说明

    /* * 请使用最新版本nodejs * 默认配置,是按生产环境的要求设置,也就是使用 webpack -p 命令就可以生成正式上线版本. * 也可以使用 webpack -d -w 命令,生成用于开 ...

  5. MediaWiki隐藏index

    Apache 在httpd.conf配置文件中加载mod_rewrite.so模块,将前面的'#'去掉,如果没有则添加这句话: #LoadModule rewrite_module modules/m ...

  6. 理解CDN

    一.CDN定义 CDN的全称是Content Delivery Network,即内容分发网络.其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快.更稳定.通过 ...

  7. ESPCMS基本导航操作

    Espcms和dedecms一样,是用来建企业站的cms程序,功能强大,稳定,可以帮助您快速.便捷地新建一个企业网站.无忧主机向您推荐无忧主机php虚拟主机. 我们可以通过espcms设置来去掉比如购 ...

  8. FastFDFS_Jave客户端调用(亲测可用)

    一.配置文件(fdfs_client.properties) 1 2 3 4 5 6 7 8 9 10 connect_timeout = 30 network_timeout = 60 charse ...

  9. 【BZOJ 2818】gcd 欧拉筛

    枚举小于n的质数,然后再枚举小于n/这个质数的Φ的和,乘2再加1即可.乘2是因为xy互换是另一组解,加1是x==y==1时的一组解.至于求和我们只需处理前缀和就可以啦,注意Φ(1)的值不能包含在前缀和 ...

  10. 树分治 poj 1741

    n k n个节点的一棵树 k是距离 求树上有几对点距离<=k; #include<stdio.h> #include<string.h> #include<algo ...