上一节我们主要讲解了手动创建字符设备节点并访问,但是感觉太过于麻烦,因此,我们这一节主要讲解如何自动创建字符设备并进行读写操作,以及讲解编写字符设备驱动的框架
不过在此之前,我们先不上代码,先了解一下基本的概念。
1、设备号dev_t
设备号为设备驱动模块程序在Linux系统中唯一识别号。其为32bits的无符号整数, 一个设备号分成主设备号和次设备号两部分:(Linux2.6版本的设备号)
主号12bits 次号20bits
一般情况下主设备号为一种设备类型,次设备号为这类设备的具体一个设备。
MKDEV(ma,mi);将ma主设备号和次设备号合成一个32bit的完整设备号;
MAJOR(dev);从一个设备号中提取出主设备号;
MINOR(dev);从一个设备号中提取次设备号;
2、struct cdev字符设备控制块
字符设备控制块struct cdev是字符设备的核心,内核就是通过对其的挂接和卸载完成字符设备工作。
3、字符设备函数操作集struct file_operations
利用struct file_operations声明实体,然后对相应的函数给出实现体,最后将该实体赋给cdev中ops的成员。
4、代码展示
看了些理论感觉很无聊,我们还是先上点代码吧
模块代码
cdevAPI.c
#include "include/kmodlue.h"
//协议声明
MODULE_LICENSE("GPL");
//声明一个节点名作为模块输入参数
//其将在/dev目录下被创建。
static char *DeviceNodeName = "ss";
module_param(DeviceNodeName, charp, 0600);
MODULE_PARM_DESC(DeviceNodeName, "The default value is \"ss\"");
dev_t dev; //存放设备号,因该理解为主设备号
struct cdev cdev; //字符设备控制块
int dsn; //次设备号起始值
int acount_of_devices; //声明次设备号数量,即具体设备数量
//To define a pointer of device class 定义一个设备类型指针
struct class *D_class;
//To define a pointer of device 定义一个设备
struct device *D_device;
//Show it in(显示在) /proc/devices file
#define DeviceName "Chr_dev"
//Show it in(显示在) /sys/class/ dir.
#define DeviceNameClass "Chr_dev_class"
#define MEM_SIZE 20 //声明内核内存空间大小
unsigned char *mem_datas; //内核内存空间指针
//API的打开程序接口
int cdev_open(struct inode *i, struct file *filp)
{
//将ChrDevp指针赋值给filp->private_data,也就是今后,
//函数struct file *参数中的filp->private_data也指向mem_datas所指向的空间。
filp->private_data = mem_datas;
printk(KERN_INFO "The device was openned by the AP!\n");
return 0;
}
//API的关闭程序接口
int cdev_close(struct inode *i, struct file *filp)
{
filp->private_data = NULL;
printk(KERN_INFO "The device was closed by the AP!\n");
return 0;
}
//定义AP程序操作接口
struct file_operations cdev_fops = {
.owner = THIS_MODULE,
.open = cdev_open,
.release = cdev_close,
};
//mount fuction 挂载函数
static int __init chr_dev_init(void)
{
int rst = 0;
dsn = 3;
acount_of_devices = 1;
//动态申请设备号
rst = alloc_chrdev_region(&dev, dsn, acount_of_devices, DeviceName);
if(rst < 0)
{
rst = -1;
printk(KERN_INFO "Fail:alloc_chrdev_region()\n");
goto Exit_1;
}
//To init the cdev blk 初始化字符控制快
cdev_init(&cdev,&cdev_fops);
cdev.owner = THIS_MODULE;
//添加设备快
rst = cdev_add(&cdev, dev, dsn+1);
if(rst < 0)
{
rst = -2;
printk(KERN_INFO "Fail:cdev_add()\n");
goto Exit_2;
}
//创建设备类型
D_class = class_create(THIS_MODULE, DeviceNameClass);
if(IS_ERR(D_class))
{
rst = -3;
printk(KERN_INFO "Fail:class_create()\n");
goto Exit_3;
}
//创建设备节点
D_device = device_create(D_class, NULL, dev, NULL, DeviceNodeName);
if(IS_ERR(D_device))
{
rst = -4;
printk(KERN_INFO "Fail:device_create()\n");
goto Exit_4;
}
//创建内核数据空间
mem_datas = kmalloc(MEM_SIZE, GFP_KERNEL);
if(IS_ERR(mem_datas))
{
rst = -5;
printk(KERN_INFO "Fail:kmalloc() for ChrDevp->mem_datas\n");
goto Exit_5;
}
//Inited is success! and exit. 初始化成功
//rst = 0;
printk(KERN_INFO "The device module was mounted by the kernel!,the device number maj:%d and min: %d\n",
MAJOR(dev), MINOR(dev));
goto Exit_1;
Exit_5:
//销毁设备节点
device_destroy(D_class, dev);
Exit_4:
//销毁设备类型
class_destroy(D_class);
Exit_3:
//删除设备块
cdev_del(&cdev);
Exit_2:
//注销设备号
unregister_chrdev_region(dev, acount_of_devices);
Exit_1://程序退出,初始化结束
return rst;
}
//umount fuction 卸载函数
static void __exit chr_dev_exit(void)
{
//释放内核内存空间
kfree(mem_datas);
//销毁设备节点
device_destroy(D_class, dev);
//销毁设备类型
class_destroy(D_class);
//删除设备块
cdev_del(&cdev);
//注销设备号
unregister_chrdev_region(dev, acount_of_devices);
printk(KERN_INFO "The device module was umounted by the kernel!\n");
}
//封装模块挂载函数
module_init(chr_dev_init);
//封装模块卸载函数
module_exit(chr_dev_exit);
应用程序代码
testap.c
#include<stdio.h>
#include<stdlib.h>
#include <fcntl.h>
int main(int c, char **v)
{
int fp = open("/dev/ss",O_RDWR);
if(fp < 0)
{
printf("Error,device no exists!\n");
return -1;
}
printf("Openning success!\n");
sleep(3);
printf("Closing success!\n");
close(fp);
return 0;
}
Makefile
include目录里面的文件
include/cmd.h
#ifndef __CMD_H__
#define __CMD_H__
#define MAGIC 0x34
#define OPEN_LED 1
#define CLOSE_LED 2
#define IO_OPEN_LED _IO(MAGIC, OPEN_LED)
#define IO_CLOSE_LED _IO(MAGIC, CLOSE_LED)
#endif
include/kmodule.h
#ifndef __KMODULE_H__
#define __KMODULE_H__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#endif
由于之前装载、卸载模块已经说明很多次,因此,这里不再进行说明,如果有不明白的地方,请参考以前的教程。
5、函数简要说明
一、动态分配设备号函数:
int alloc_register_region(dev_t *dev, unsigned basemonor, unsigned count, const char *name);
返回值:0表示成功,非0表示失败。
输入参数:
dev,存放获得的设备号,指针方式传入;
baseminor,第一个次设备号,;
count,总共次设备号数量;
name,被分配的设备或驱动名称。
![](/qrcode.jpg)
二、创建设备类型目录及相应配置函数:
struct class *class_create(owner, name);
返回值:为struct class指针,可用IS_ERR()函数来判断返回是否成功。
输入参数:
owner ,一般使用THIS_MODULE参数;
name,设备类型名称。
会在/sys/class中创建name参数的目录及一些文件。
三、自动创建设备节点函数:
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, …);
返回值:struct device *指针,可用IS_ERR()函数来判断返回是否成功。
class,为用class_create()得到的指针;
parent,指向父级设备,一般可以为NULL;
devt,设备号;
drvdata,为回调函数,或携带数据,一般为NULL;
fmt,为设备类型名称。
自动会在/dev目录下创建fmt设备节点。
四、删除设备节点函数:
void device_destroy(struct class *class, dev_t devt);
返回参数:空。
输入参数:
class,要删除设备节点所属的设备类型;
devt,要删除的设备号。
五、删除设备类型函数:
void class_destroy(struct class *cls);
返回值:空。
输入参数:
cls,要删除的设备类型。
六、开发字符设备并读写的框架流程
初始化
1、动态申请设备号 alloc_register_region
2、初始化字符设备块 cdev_init
3、向内核添加设备块 cdev_add
4、创建设备类型 class_create
5、创建设备节点 device_create
6、创建内核数据空间 kmalloc
卸载
1、释放内核内存空间 kfree
2、销毁设备节点 device_destroy
3、销毁设备类型 class_destroy
4、注销设备号 unregister_chrdev_region
我们在insmod模块的时候,模块初始化会自动创建设备节点。通过设备节点,应用程序就可以进行正常的数据操作了。read write open close等等