字符设备驱动基础1——简单的驱动源代码分析

以下内容源于朱有鹏嵌入式课程的学习,如有侵权请告知删除。

参考博客:linux驱动开发(一) - biaohc - 博客园

一、驱动源代码示例

/********module_test.c代码*********/

#include <linux/module.h>	// module_init  module_exit
#include <linux/init.h>		// __init   __exit
#include <linux/fs.h>

//这里手动定义主设备号,之前必须确认200没有被使用,查看方法:cat /proc/devices。
//比较好的方法是内核自动分配。
#define MYMAJOR	 200
#define MYNAME		"testchar"

static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
	printk(KERN_INFO "test_chrdev_open\n");
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	return 0;
}

// 自定义一个file_operations结构体变量,并且去填充.
//这里的.只是结构体变量初始化的一种方法而已。
//file_operations是内核中已经定义好的结构体。
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,		// 惯例,直接写即可
	.open		= test_chrdev_open,	// 将来应用open打开这个设备时
                                    // 实际调用的就是这个.open对应的函数
	.release	= test_chrdev_release,		
};


// 模块安装函数
static int __init chrdev_init(void)
{	
	int ret = -1;

	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 在module_init宏调用的函数中去注册字符设备驱动
	ret = register_chrdev(MYMAJOR, MYNAME, &test_fops);
	if (ret)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success...\n");

	return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	
	// 在module_exit宏调用的函数中去注销字符设备驱动
	unregister_chrdev(MYMAJOR, MYNAME);
	
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

二、解释说明

1、常用的模块操作命令

lsmod命令(list module,将模块列表显示)

功能是打印出当前内核中已经安装的模块列表。

insmod命令(install module,安装模块)

功能是向当前内核中安装一个模块,用法是insmod xxx.ko

modinfo(module information,模块信息)

功能是打印出一个内核模块的自带信息,用法是modinfo xxx.ko。

rmmod(remove module,卸载模块)

功能是从当前内核中卸载一个已经安装的模块,用法是rmmod xxx(不加.ko后缀)。

2、模块的安装与卸载

(1)模块的安装:insmod与module_init宏

执行insmod命令时,insmod内部实际执行的是使用module_init宏所声明的函数。

比如insmod module_test.ko时,insmod命令内部实际执行的操作是调用chrdev_init函数

(2)模块的卸载:rmmod和module_exit宏

和上面阐述的是同一个道理,可以使用lsmod命令查看rmmod前后系统的模块记录变化。

3、模块的版本信息

(1)使用modinfo命令查看模块的版本信息

root@ubuntu:/home/xjh/iot/embedded_basic/rootfs/tmp# modinfo module_test.ko 
filename:       /home/xjh/iot/embedded_basic/rootfs/tmp/module_test.ko
alias:          alias xxx
description:    module test
author:         aston
license:        GPL
depends:  //这是依赖,比如usb-wifi的驱动依赖usb的驱动      
vermagic:       2.6.35.7 preempt mod_unload ARMv7  //这是版本号
root@ubuntu:/home/xjh/iot/embedded_basic/rootfs/tmp#

(2)驱动的版本信息与内核的版本信息要一致

使用insmod命令安装驱动模块时,驱动模块的版本信息要与内核版本信息一致。这是一种安全措施,目的是为了保证模块和内核的兼容性。如果不一致,将报错:

insmod: ERROR: could not insert module module_test.ko: Invalid module format;

如何保持一致?确保某个内核源码树,既用来生成用来烧录的内核镜像,也用来编译驱动模块。

4、模块中常用宏

MODULE_xxx,这些宏用来添加驱动模块描述信息。

(1)MODULE_LICENSE,模块的许可证。

一般声明为GPL许可证,缺少可能会出现莫名其妙的错误(一些明显存在的函数提升找不到)。

(2)MODULE_AUTHOR,编写作者。

(3)MODULE_DESCRIPTION,模块描述。

(4)MODULE_ALIAS,别名信息。

5、函数修饰符__init、__exit

(1)函数修饰符__init

1)本质

__init 本质上是宏定义,在内核源代码中有#define __init xxxx。

作用

它的作用,是将所修饰的函数放入.init.text段(默认情况下函数被放入.text段中)。

使用这个修饰符有什么好处呢?

被__init 所修饰的函数,都被链接器链接放入.init.text段中。内核启动时会统一加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存(因为安装完后这些函数就没用了)。

补充说明

下划线越多,越深入内核深处。

(2)函数修饰符__exit

和__init类似的意义,不赘述。

6、printk函数详解

(1)printk函数作用

在内核源码中用来打印信息的函数。

(2)printk和printf的差别

printf是C库函数,在应用层编程中使用,不能在linux内核源代码中使用;

printk是内核源码中的一个普通函数,只能在内核源码范围内使用,不能在应用编程中使用。

(3)printk可以设置打印级别

1)打印级别的意义

应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译(DEBUG宏)来实现;但是内核非常庞大,打印信息非常多,因此需要设置打印级别。printk的打印级别,可以用来控制printk打印的这条信息是否在终端上显示的。

2)打印级别

3)查看打印级别

可以使用“cat /proc/sys/kernel/printk”查看打印级别。

4)设置打印级别

可以用“echo 4 /proc/sys/kernel/printk”设置打印级别为4。 

5)命令行设置打印信息级别

在命令行中也可以设置打印信息级别,级别值为0-7。在执行printk的时候,对比printk中的打印级别与命令行中设置的打印级别,发现printk小于命令行设置级别的信息会被放行打印出来,大于的就被拦截。但ubuntu中不管如何设置级别,都不能直接打印出来,必须使用dmesg命令去查看。

7、关于驱动模块中的头文件

驱动源代码中包含的头文件,和应用程序中包含的头文件不同。

应用程序所包含的头文件,是应用层的头文件,一般是编译器带来的,与操作系统无关。比如 gcc 的头文件路径在 /usr/include下。

驱动源码所包含的头文件,是内核源代码include目录下的头文件,属于内核源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值