嵌入式Linux-字符设备驱动

概述

Linux驱动开发一共分为三大类:
1.字符设备驱动
2.块设备驱动
3.网络设备驱动
字符设备驱动最多,而块设备驱动与网络设备驱动开发复杂,一般由半导体厂商提供。

字符设备驱动

字符设备是 Linux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字 节流进行读写操作的设备,读写数据是分先后顺序的。
Linux下的应用程序调用驱动程序流程(从1到5):
1.应用程序:open(),close(),read(),write()等API函数
2.库:对应的库函数
3.内核:通过系统调用进入内核
4.驱动程序:驱动程序中的open(),close(),open(),release()函数
5.硬件:具体硬件设备

驱动模块的加载与卸载

Linux驱动有两种运行方式,第一种就是将驱动编译进 Linux内核中,这样当 Linux内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用“insmod”命令加载驱动模块。
模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

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

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

1 /*驱动入口函数 */
2 static int __init xxx_init(void) //static 静态函数,仅仅本文件可以被调用
3 {
4 /*入口函数具体内容 */
5	 return 0;
6 }
7
8 /*驱动出口函数 */
9 static void __exit xxx_exit(void)
10 {
11 /*出口函数具体内容 */
12 }
13
14 /*将上面两个函数指定为驱动的入口和出口函数 */
15 module_init(xxx_init);
16 module_exit(xxx_exit);

驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块:insmod和 modprobe,insmod是最简单的模块加载命令,此命令用于加载指定的.ko模块。insmod命令不能解决模块的依赖关系。比如 drv.ko依赖 first.ko这个模块,就必须先使用insmod命令加载 first.ko这个模块,然后再加载 drv.ko这个模块。但是 modprobe就不会存在这个问题,modprobe会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此modprobe命令相比 insmod要智能一些。modprobe命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能,推荐使用 modprobe命令来加载驱动。

字符设备注册于注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模
块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:

//inline放在函数返回类型前修饰函数,被修饰的函数就叫做内联函数,把函数的代码复制到每一个调用处。
//这样调用函数的过程就可以直接执行函数代码,而不发生跳转、压栈等一般性函数操作。可以节省时间,
//也会提高程序的执行速度。
//常量指针所指的内容不能变
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)

字符设备的具体操作函数

初始化 open、release、read和 write等具体的设备操作函数

1 /*打开设备 */
2 static int chrtest_open(struct inode *inode, struct file *filp)
3 {
4 /*用户实现具体功能 */
5 return 0;
6 }
7
8 /*从设备读取 */
9 static ssize_t chrtest_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
10 {
11 /*用户实现具体功能 */
12 return 0;
13 }
14
15 /*向设备写数据 */
16 static ssize_t chrtest_write(struct file *filp,
const char __user *buf,
size_t cnt, loff_t *offt)
17 {
18 /*用户实现具体功能 */
19 return 0;
20 }
21
22 /*关闭/释放设备 */
23 static int chrtest_release(struct inode *inode, struct file *filp)
24 {
25 /*用户实现具体功能 */
26 return 0;
27 }
28
29 static struct file_operations test_fops = {
30 .owner = THIS_MODULE,
31 .open = chrtest_open,
32 .read = chrtest_read,
33 .write = chrtest_write,
34 .release = chrtest_release,
35 };

添加 LICENSE和作者信息

最后我们需要在驱动中加入 LICENSE信息和作者信息,其中 LICENSE是必须添加的,否则的话编译的时候会报错

MODULE_LICENSE() //添加模块 LICENSE信息
MODULE_AUTHOR() //添加模块作者信息

设备号

Linux中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux提供一个dev_t的数据类型表示设备号,dev_t实际就是unsigned int类型。高12位表示主设备号,低20位表示次设备号。
Linux社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
//atoi()函数可以将字符串的数字转换成真实的数字

物理地址到虚拟地址的映射

ioremap函数用于获取指定物理地址空间对应的虚拟地址空间,卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射。再像裸机那样做正常的IO设置。

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) 
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype) 
{
 return arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}
void iounmap (volatile void __iomem *addr)

新字符设备的驱动

分配和释放设备号

使用 register_chrdev函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会带来两个问题 。1.需要我们事先确定好哪些主设备号没有使用。2.会将一个主设备号下的所有次设备号都使用掉。解决这两个问题最好的方法就是要使用设备号的时候向 Linux内核申请,需要几个就申请几个,由 Linux内核分配设备可以使用的设备号。
如果没有指定设备号的话就使用如下函数来申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号:

int register_chrdev_region(dev_t from, unsigned count, const char *name) 

注销字符设备之后要释放掉设备号 :

void unregister_chrdev_region(dev_t from, unsigned count) 

新的字符设备注册方法

Linux中使用 cdev结构体表示一个字符设备:

1 struct cdev { 
2    struct kobject  kobj; 
3    struct module   *owner; 
4    const struct file_operations *ops; 
5    struct list_head  list; 
6    dev_t     dev; 
7    unsigned int   count; 
8 };

定义好 cdev变量以后就要使用 cdev_init函数对其进行初始化,cdev_init函数原型如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
//参数 cdev就是要初始化的 cdev结构体变量,参数 fops就是字符设备文件操作函数集合

cdev_add函数用于向 Linux系统添加字符设备(cdev结构体变量),cdev_add函数原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count) 
//参数 p指向要添加的字符设备(cdev结构体变量),参数 dev就是设备所使用的设备号,参数 count是要添加的设备数量

卸载驱动的时候一定要使用 cdev_del函数从 Linux内核中删除相应的字符设备,cdev_del函数原型如下:

void cdev_del(struct cdev *p) 

自动创建设备节点

在嵌入式 Linux中我们使用mdev来实现设备节点文件的自动创建与删除
创建和删除类:

struct class *class_create (struct module *owner, const char *name);
void class_destroy(struct class *cls); 

创建和删除设备:

struct device *device_create(struct class  *class,  
struct device  *parent,  
dev_t   devt,  
void    *drvdata,  
const char  *fmt, ...);
void device_destroy(struct class *class, dev_t devt) ;

参考例程:

1  struct class *class;   /* 类   */        
2  struct device *device;    /* 设备  */ 
3  dev_t devid;           /* 设备号  */          
4   
5  /* 驱动入口函数 */  
6  static int __init led_init(void) 
7  { 
8    /* 创建类  */ 
9    class = class_create(THIS_MODULE, "xxx"); 
10    /* 创建设备 */ 
11   device = device_create(class, NULL, devid, NULL, "xxx"); 
12   return 0; 
13 } 
14  
15 /* 驱动出口函数 */ 
16 static void __exit led_exit(void) 
17 { 
18   /* 删除设备 */ 
19   device_destroy(newchrled.class, newchrled.devid); 
20   /* 删除类   */ 
21   class_destroy(newchrled.class); 
22 } 
23  
24 module_init(led_init); 
25 module_exit(led_exit);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值