为了方便后续的深入,我们在驱动程序中用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. OFFSET IN 使用举例

    本文将结合具体实例阐述OFFSET IN的使用方法.注意:这是我第一次写OFFSET IN约束,本文仅供参考.阅读本文前需要了解时序收敛的基本概念,OFFSET IN和Period的相关知识,可先阅读 ...

  2. 在CentOS上部署基于dnx/coreclr的ASP.NET 5应用程序

    在Ubuntu上写好了一个简单的ASP.NET 5应用程序,尝试将这个程序部署在没有mono环境的CentOS服务器上. 部署步骤如下: 1)安装libuv(KestrelHttpServer需要它) ...

  3. 20160223 - Windows 10 的文件资源管理器下出现重复文件夹的解决办法

    现象: 安装 OneDrive 从 Windows 7.8.8.1 升级来的 Windows 10 的电脑,可能会出现文件资源管理器左侧面板中出现重复的文件夹. 通常有:视频.图片.文档.下载.音频. ...

  4. jquery 选择器大全

    jquery 选择器大体上可分为4 类: 1.基本选择器 2.层次选择器 3.过滤选择器 4.表单选择器 其中过滤选择器可以分为: 1.简单过滤选择器 2.内容过滤选择器 3.可见性过滤选择器 4.属 ...

  5. ArcGIS支持MongoDB数据源

    ArcGIS支持MongoDB数据源 自从NoSQL推出之后,MongoDB就作为比较杰出的代表受到广大用户的推崇,当然,与之而来的大数据的讨论也非常激烈,GIS数据源向来都是以海量来计算,所以,GI ...

  6. 分布式拒绝服务攻击 DDoS

    分布式拒绝服务(DDoS:Distributed Denial of Service)攻击指借助于客户/服务器技术,将多个计算机联合起来作为攻击平台,对一个或多个目标发动DDoS攻击,从而成倍地提高拒 ...

  7. JS 问题集锦

    [1]js页面跳转 和 js打开新窗口方法 第一种: <script language="javascript" type="text/javascript&quo ...

  8. RHCE认证考试教材

    前段时间考RHCE7,顺便给大家分享下RHCE6.7的中文教材!毕竟此书是官方的培训教材,还是值得看看!RHEL6.7承前启后的,给个赞! 下载:http://pan.baidu.com/s/1nu9 ...

  9. linux基础-第六单元 用户、群组和权限

    用户及passwd文件 /etc/passwd文件的功能 /etc/passwd文件每个字段的具体含义 shadow文件 /etc/shadow文件的功能 /etc/shadow文件每个字段的具体含义 ...

  10. 利用反射,泛型,静态方法快速获取表单值到Model

    在项目中经常需要处理表单,给model赋值,很烦人的一些重复代码.如下边的代码: News news = new News(); news.Id = int.Parse(Request.Form[&q ...