第1个linux驱动___打印"hello world"
为了方便后续的深入,我们在驱动程序中用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"的更多相关文章
- Linux驱动学习之常用的模块操作命令
1.常用的模块操作命令 (1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 (2)insmod(install module,安装模块),功能是向当前 ...
- 嵌入式Linux驱动开发日记
嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...
- Linux驱动学习步骤(转载)
1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ls ...
- Linux驱动设计—— 驱动调试技术
参考博客与书籍: <Linux设备驱动开发详解> <Linux设备驱动程序> http://blog.chinaunix.net/uid-24219701-id-2884942 ...
- Linux驱动开发之开篇--HelloWorld
Linux驱动的编写,大致分为两个过程,第一个过程为测试阶段,即为某一具体的设备,添加必要的驱动模块,为了节省编译时间,需要将代码单独放在一处,在编译时,只需要要调用内核的头文件即可:第二个过程为布置 ...
- Linux驱动开发 -- 打开dev_dbg()
Linux驱动开发 -- 打开dev_dbg() -- :: 分类: LINUX linux设备驱动调试,我们在内核中看到内核使用dev_dbg来控制输出信息,这个函数的实质是调用printk(KER ...
- Linux驱动开发学习的一些必要步骤
1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...
- Android系统移植与驱动开发——第六章——使用实例来理解Linux驱动开发及心得
Linux驱动的工作方式就是交互.例如向Linux打印机驱动发送一个打印命令,可以直接使用C语言函数open打开设备文件,在使用C语言函数ioctl向该驱动的设备文件发送打印命令.编写Linux驱动最 ...
- linux驱动调试--段错误之oops信息分析
linux驱动调试--段错误之oops信息分析 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id= ...
随机推荐
- 我们来八一八阿里云OS的实质和历史
有个姓许的朋友在微信公众号上这样评论: 但是楼主对yunos的了解程度有多少,建议去了解下再评价别人,免费给你普及下:http://www.ithome.com/html/digi/109484.ht ...
- AR播放器
一.项目需求 AR播放器:将一系列带透明通道的图片以一定的帧率连续显示,叠加载摄像头采集的画面之上,并播放声音. 此为最初级的AR技术,因为画面是事先渲染好的,固定不变的,所以实际上并不能实现“互动” ...
- Node基础:url查询参数解析之querystring
模块概述 在nodejs中,提供了querystring这个模块,用来做url查询参数的解析,使用非常简单. 模块总共有四个方法,绝大部分时,我们只会用到 .parse(). .stringify() ...
- Windows8.1画热度图 - 坑
想要的效果 如上是silverlight版本.原理是设定一个调色板,为256的渐变色(存在一个png文件中,宽度为256,高度为1),然后针对要处理的距离矩阵图形,取图片中每个像素的Alpha值作为索 ...
- 同态加密-Homomorphic encryption
同态加密(Homomorphic encryption)是一种加密形式,它允许人们对密文进行特定的代数运算得到仍然是加密的结果,将其解密所得到的结果与对明文进行同样的运算结果一样.换言之,这项技术令人 ...
- RabbitMQ官方中文入门教程(PHP版) 第二部分:工作队列(Work queues)
工作队列 在第一篇教程中,我们已经写了一个从已知队列中发送和获取消息的程序.在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Works ). 工作队列 ...
- C#中的interface
接口(interface) 接口泛指实体把自己提供给外界的一种抽象化物(可以为另一实体),用以由内部操作分离出外部沟通方法,使其能被修改内部而不影响外界其他实体与其交互的方式. 接口实际上是一个约定: ...
- $(document).ready()和window.onload的区别
来源于: The window.onload event fires when a document is completely downloaded to the browser. This mea ...
- Android 判断当前屏幕是横屏还是竖屏
记录学习 /** * 返回当前屏幕是否为竖屏. * @param context * @return 当且仅当当前屏幕为竖屏时返回true,否则返回false. */ public s ...
- git rebase 和 reset的区别
check the command detail by input 'git command --help' rebase: reset: