linux字符设备驱动分析

1.linux有哪些驱动?

字符设备驱动

块设备驱动

网络设备驱动

2.字符设备是什么?

字符设备是一种按字节来访问的设备,常见的有LED驱动,按键驱动,串口,IIC,SPI,LCD。

字符设备驱动通常要实现以下功能:打开驱动,关闭驱动,读写。例如:

打开LED驱动(LED初始化)

关闭LED驱动(LED反初始化)

读(读取LED状态)

写(设置LED状态)

3.块设备是什么?

大部分系统中的定义:以块(通常是512字节)为最小传输单位的设备。

而Linux则允许块设备传送任意数目的字节。因此, 块和字符设备的区别仅仅是驱动的与内核的接口 不同。常见的块设备包括硬

硬盘,flash,SD卡。

4.网络设备是什么?

可以是一个硬件设备,如网卡; 但也可 以是一个纯粹的软件设备, 比如回环接口(lo). 一个网络接口负责发送和接收数据报文。

5.其他设备分类方法?

当然也有其他分类方法,如按照总线的分类可以分为USB设备,PCI设备,平台总线设备。

接下来重点介绍字符设备驱动

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块。

驱动模块的加载命令:insmod 

驱动模块的卸载命令:rmmod 

假设我现在已经编译好了一个字符设备模块Led.ko

我就可以使用insmod  led.ko 来加载这个模块,加载完成之后就可以对这个led进行操作。当不需要使用led时可以使用rmmod led.ko来卸载这个led驱动模块

问题一:谁来响应这个insmod和remmod命令?

在程序中我们需要有函数来响应这两个命令分别为

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“insmod”命令加载驱动的时候,xxx_init 这个函数就会被调用。

module_exit()函数用来向 Linux 内核卸载一个模块卸载函数,参数 xxx_exit 就是需要卸载的具体函数,当使
用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

字符设备驱动模块加载和卸载模板如下所示:

 /* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */

    return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
 {
/* 出口函数具体内容 */
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
 module_init(xxx_init);
 module_exit(xxx_exit);

问题二:加载完成之后要做什么?即xxx_init中要实现什么?xxx_init中要实现什么?

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。

注册设备函数static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

销掉设备函数static inline void unregister_chrdev(unsigned int major, const char *name)

register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
major :主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,关于设备号后面会详细讲解。
name:设备名字,指向一串字符串。
fops :结构体 file_operations 类型指针,指向设备的操作函数集合变量。
unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
major :要注销的设备对应的主设备号。
name :要注销的设备对应的设备名。

示例代码

static struct file_operations test_fops;

/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
 int retvalue = 0;

 /* 注册字符设备驱动 */
retvalue = register_chrdev(200, "chrtest", &test_fops);
if(retvalue < 0){
/*  字符设备注册失败, 自行处理 */
}
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(200, "chrtest");
}

 /* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
 module_exit(xxx_exit);

其中file_operations是设备操作集

问题三:设备操作集是什么?

在linux中应用程序通过read write oped函数来操作字符设备驱动,那字符设备驱动中怎么来响应这些函数呢?

设备操作集就是用来响应这些函数的。

Struct file_operations是一个函数指针的集合,定义能在 设备上进行的操作。结构中的函数指针指向驱动中的函数, 这些函数实现一个针对设备的操作, 对于不支持的操作则设 置函数指针为 NULL。例如

struct file_operations dev_fops = {

.llseek = NULL,

.read = dev_read,

.write = dev_write,

.ioctl = dev_ioctl,

.open = dev_open,

.release = dev_release,

};

注:file_operations中还有其他函数这里只是列出常用的几个。

例如如果实现dev_open需要

1.查看file_operations结构体中oped函数的原型:int (*open) (struct inode *, struct file *);

2.编写自己的oped函数chrtest_open

static int chrtest_open(struct inode *inode, struct file *filp)

 {
 /*  用户实 现具体功能 */
 return 0;
 }

3.将.oped与chrtest_open对应起来

struct file_operations dev_fops = {

.open = chrtest_open,

};

注:应用程序中使用oped函数会调用file_operations中的oped函数,而.open = chrtest_open使file_operations中的oped函数指向chrtest_open。因此最后chrtest_open函数就是应用程序中oped的响应函数。

问题四:主设备号是什么?

linux中每一个字符设备都有自己的主设备号和次设备号。

主设备号用来区分驱动功能如:LED驱动和串口驱动。

此设备号用来区分硬件不同:如串口一和串口三。

Linux内核中使用dev_t类型来定义设备号,dev_t这种类型其 实质为32位的unsigned int,其中高12位为主设备号,低20 位为次设备号.

问1:如果知道主设备号,次设备号,怎么组合成dev_t类型

答:dev_t dev = MKDEV(主设备号,次设备号)

问2: 如何从dev_t中分解出主设备号?

答: 主设备号 = MAJOR(dev_t dev)

问3: 如何从dev_t中分解出次设备号?

答: 次设备号=MINOR(dev_t dev)

问题五如何为驱动分配一个设备号?

静态申请 开发者自己选择一个数字作为主设备号,然后通过函数 register_chrdev_region向内核申请使用。缺点:如果申请 使用的设备号已经被内核中的其他驱动使用了,则申请失败。

动态分配 使用alloc_chrdev_region由内核分配一个可用的主设备号。 优点:因为内核知道哪些号已经被使用了,所以不会导致分 配到已经被使用的号。

设备号注销:不论使用何种方法分配设备号,都应该在驱 动退出时,使用unregister_chrdev_region 函数释放这些设备号。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:
dev:保存申请到的设备号。
baseminor :次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count :要申请的设备号数量。
name:设备名字。
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)此函数有两个参数:
from:要释放的设备号。
count :表示从 from 开始,要释放的设备号数量。

问题六:还要进行什么工作?

最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否
则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加使用
如下两个函数:
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息

如:

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

 

加入头文件即可

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>

#include <linux/init.h>
#include <linux/module.h>

 

 

总结:

使用insmod  led.ko 来加载这个模块

使用rmmod led.ko来卸载这个led驱动模块

module_init(xxx_init); //加载模块响应函数
module_exit(xxx_exit); //注册模块响应函数

xxx_init中实现注册设备函数register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

xxx_exit中实现注销设备函数unregister_chrdev(unsigned int major, const char *name)

 

注册设备函数需要设备号,设备名字和设备操作集。

注销设备函数需要设备号和设备名字。

 

设备号有主设备号和次设备号之分,有静态分配和动态分配两种方式。

设备操作集是对应用程序使用oped等函数的响应

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值