Linux2.6(cdev) 的开发
目录
1、生成设备文件所需要的类结构体(class_create)
编写一个 Linux2.6 的代码->蜂鸣器驱动(整体代码)
回顾
-- 1:何为杂项
miscdevice 不想分类的设备!
这种方法注册驱动设备目前是最简单!
注册完毕后自动生成了一个设备文件!
实际上其他的注册方法则不会!
-- 2:什么是驱动、Linux 驱动有什么特点
我认为驱动是:驱使硬件正常工作的代码或者程序
通用、统一接口、设备抽象成文件!
-- 设备文件有什么特点
属于系统的特殊文件之一
需要用非缓冲区操作
一般在/dev/xxxxx
有设备号:uint32_t 数字
分为 主设备号(高 12bit)
次设备号(低 20bit)
-- 什么是特殊文件?(普通人见不到的文件,不是开发人员见不到)
-- 特殊文件需要非缓冲区操作?
-- 普通文件都是需要缓冲区,因为容易造成浪费?
-- 为什么杂项注册时候,只提供次设备号
因为主设备号固定为 10
-- 主设备号和次设备号都是0-255(但这是32位的芯片,64位的不一定)
-- 何为 GPIO 子系统?
中间层:系统内核自己编写的统一接口
-- GPIO 子系统的 gpio 编号是如何计算的!
瑞芯微: 32大组号 + 8 小组号 + 本身编号
-- !!!A系列的芯片要取代单片机(比如人脸识别,单片机就不可以)
-- 目前海思,瑞星微,全智这几个厂商比较可以
Linux2.6(cdev) 的开发
-- 这是最标准的驱动开发
-- 对比于杂项而言,Linux2.6 的开发:
- 1:开发较为复杂一些
- 2:设备号需要用户自己申请
- 3:设备注册完毕后不会生成,需要用户自己生成
其他特点完全等于与杂项设备,同样设备号不再限制了!
了解一下 Linux2.6 开发框架
- 1:先去申请设备号-> alloc_chrdev_region->连续申请多个设备号
- 2:再去拿申请的设备号注册设备->连续注册多个设备 cdev_init cdev_add
- 3:Linux2.6 注册完毕后 不会生成设备文件的,我们需要手动生成 class_create device_create
学习 Linux2.6 的相关接口
1、申请设备号(alloc_chrdev_region)
-- 函数的功能:在内核中 申请一个/多个可用的设备号
-- 函数的头文件:<linux/fs.h>
-- 函数的原型:int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name);
-- 函数的参数:
- dev_t *dev:申请的设备号,用户自己定义一个变量,函数会自动赋值!(dev_t == unsigned int)
就是你申请得到设备号存放的位置! 如果你申请多个设备号,那么它只存放你申请得到的第一个设备号!
-
baseminor:次设备号,你打算从哪个次设备号开始申请,随便填写 ,可以填写99
-
count:你申请得到的数量
举个例子:如果 你 baseminor 填的是 99 count 填写的是 3
如果此时系统返回的设备号主设备号 为 88
那么 就代表你申请了三个设备号返回了:
主设备号 次设备号
88 99 ->第一个设备号存放于 第一个参数空间
88 100
88 101
- label:标签是无所谓的一个名字
-- 函数返回值:
- 如果申请成功返回0
- 申请失败返回非 0
2、初始化 cdev 结构体(cdev_init)
-- 函数的功能:初始化 cdev 结构体
-- 函数的头文件:<linux/cdev.h>
-- 函数的原型:void cdev_init(struct cdev *cdev, const struct file_operations *fops);
-- 函数的参数:
- cdev:用户自己定义一个变量,函数会自动赋值!
类似昨天的 杂项结构体
不同的是 这个结构体不需要像杂项一样,需要我们自己初始化
这个结构体你只需要申请这样的变量、或者开辟这样的空间
传入该函数里面即可!
靠函数做初始化工作!
就是 cdev 核心结构体->Linux2.6 核心结构体
- fops:
大家都在杂项见过,他就是内核层的文件接口
-- 函数返回值:无
3、注册设备(cdev_add)
-- 函数的功能:注册设备
-- 函数的头文件:<linux/cdev.h>
-- 函数的原型:int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
-- 函数的参数:
-
cdev:毫无疑问,他就是你已经完成初始化工作的 cdev 核心结构体
-
dev:你要告诉我你接下来注册的设备的!!!!->首设备号
-
count: 你要连续注册几个设备!
举个例子:
如果你提供的 dev : 主设备号为
88 次设备号 88
你的 count 提供的是 3
那么 你将向内核连续注册三个设备:
分别为:
主设备号 次设备号
88 88
88 89
88 90
-- 函数返回值:
- 成功返回 0
- 失败返回 非 0
编写一个 Linux2.6 的代码->蜂鸣器驱动
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
dev_t devnumber;
struct cdev mydev;
struct file_operations ops;
int mybeep_open(struct inode *i, struct file *f)
{
gpio_set_value(36,1);
return 0;
}
int mybeep_close(struct inode *i, struct file *f)
{
gpio_set_value(36,0);
return 0;
}
static int __init mybeep_init(void)
{
//1:申请一个可以用的设备号
int ret = alloc_chrdev_region(&devnumber,88,1,"beep");
if(ret < 0 )
{
printk("申请失败!\r\n");
return -EINVAL;//如果你返回的是这个东西则代表加载失败 insmod加载失效
}
printk("申请成功,mydevnum = %d\r\n",devnumber);
printk("主设备号 === %d\r\n",devnumber >> 20);//主设备号是设备号的高12位,现在将设备号向右移20位,将高12位放在低位,得到主设备号
printk("次设备号 === %d\r\n",devnumber & 0xFFFFF);//次设备号是设备号的低20位,这里是将低20位与出来,然后将高12位清0.(16进制一个F是4位,2*5 = 20位)
//2:初始化 cdev
ops.owner = THIS_MODULE;
ops.open = mybeep_open;
ops.release = mybeep_close;
cdev_init(&mydev,&ops);
//3:往内核里面添加cdev 结构体
ret = cdev_add(&mydev,devnumber,1);
if(ret <0)
{
printk("注册设备失败!\r\n");
return -EINVAL;
}
printk("设备注册成功!\r\n");
//4:申请GPIO 设置GPIO
gpio_request(36,"beep");
gpio_direction_output(36,0);
return 0;
}
static void __exit mybeep_exit(void)
{
}
module_init(mybeep_init);
module_exit(mybeep_exit);
MODULE_LICENSE("GPL");
-- 编译成功后,加载驱动程序,查看设备号!
-- 连接串口
-- 可以看出正常生成了设备号
-- 但是查看设备文件,发现没有生成设备文件!
ls /dev/myb*
-- 可以看出虽然注册到了设备号,但是没有生成设备文件!,所以我们需要手动生成设备文件!
手动生成设备文件的指令和接口
mknod /dev/mybeep c 234 88
-- 然后在main.c中写入打开这个设备文件
-- 但是手动生成设备文件没有什么意义,我们还是需要写一个驱动程序,自动生成设备文件!
结合 Linux2.6 写一个自动生成设备文件的驱动
1、生成设备文件所需要的类结构体(class_create)
-- 函数的功能:生成一个类结构体
-- 函数的头文件:<linux/device.h>
-- 函数的原型:struct class *class_create(struct module *owner, const char *name);
-- 函数的参数:
-
owner:只要你在内核见到这个东西,填写 THIS_MODULE
-
name:无所谓,生成类的标识名字
-- 函数返回值:返回的就是你创建的类结构体, 这个类结构体用于生成设备文件的函数的传参!
2、生成设备文件(device_create)
-- 函数的功能:在内核里面创建一个设备文件
-- 函数的头文件:<linux/device.h>
-- 函数的原型:struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
-- 函数的参数:
-
class:他就是刚才我们所讲到 类结构体,class_create 创建的结构体‘
-
parent:父设备,一般填写 NULL
-
devt:你要创建的设备文件的设备号
-
drvdata:你要创建的设备文件的设备号
-
fmt:printf();这就是你的 printf 格式化传参字符串!
可以直接传字符串
他就是你的生成的设备文件的名字
只不过支持格式化传递!
-- 函数返回值:
用于设备的销毁 也可以不要
-- 在代码中加入
//4:先去搞一个 类结构体
cls = class_create(THIS_MODULE,"beep19");
//5:生成设备文件
device_create(cls,NULL,devnumber,NULL,"xydbeep");
补充:那个卸载函数采用倒序
-- 也就是先申请设备号函数,就最后取消申请设备号函数。
--
static void __exit myled_exit()
{
//符合倒叙
gpio_free(36);
device_destroy(cls,mydevnum);
class_destroy(cls);
cdev_del(&mycdev);
unregister_chrdev_region(mydevnum,1);
}
小结:
- 申请设备号函数:
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
- 取消申请的设备号
void unregister_chrdev_region(dev_t, unsigned);
- cdev 的结构体的初始化
cdev_init
- cdev 结构体的注册
cdev_add
- cdev 结构体删除
cdev_del
- 类结构体的创建
class_create(THIS_MODULE,"xyd_class");
- 类结构体的销毁
class_destroy(cls);
- 设备文件的创建
device_create(cls,NULL,mydevnum,NULL,"xydbeep");
//就会生成一个 设备文件 /dev/xydbeep
- 设备文件的销毁
device_destroy(cls,mydevnum);
编写一个 Linux2.6 的代码->蜂鸣器驱动(整体代码)
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
dev_t devnumber;
struct cdev mydev;
struct file_operations ops;
struct class * cls;//类结构体
int mybeep_open(struct inode *i, struct file *f)
{
gpio_set_value(36,1);
return 0;
}
int mybeep_close(struct inode *i, struct file *f)
{
gpio_set_value(36,0);
return 0;
}
static int __init mybeep_init(void)
{
//1:申请一个可以用的设备号
int ret = alloc_chrdev_region(&devnumber,88,1,"beep");
if(ret < 0 )
{
printk("申请失败!\r\n");
return -EINVAL;//如果你返回的是这个东西则代表加载失败 insmod加载失效
}
printk("申请成功,mydevnum = %d\r\n",devnumber);
printk("主设备号 === %d\r\n",devnumber >> 20);//主设备号是设备号的高12位,现在将设备号向右移20位,将高12位放在低位,得到主设备号
printk("次设备号 === %d\r\n",devnumber & 0xFFFFF);//次设备号是设备号的低20位,这里是将低20位与出来,然后将高12位清0.(16进制一个F是4位,2*5 = 20位)
//2:初始化 cdev
ops.owner = THIS_MODULE;
ops.open = mybeep_open;
ops.release = mybeep_close;
cdev_init(&mydev,&ops);
//3:往内核里面添加cdev 结构体
ret = cdev_add(&mydev,devnumber,1);
if(ret <0)
{
printk("注册设备失败!\r\n");
return -EINVAL;
}
printk("设备注册成功!\r\n");
//4:先去搞一个 类结构体
cls = class_create(THIS_MODULE,"beep19");
//5:生成设备文件
device_create(cls,NULL,devnumber,NULL,"xydbeep");
//6:申请GPIO 设置GPIO
gpio_request(36,"beep");
gpio_direction_output(36,0);
return 0;
}
static void __exit mybeep_exit(void)
{
gpio_free(36);
device_destroy(cls,devnumber);
class_destroy(cls);
cdev_del(&mydev);
unregister_chrdev_region(devnumber,1);
}
module_init(mybeep_init);
module_exit(mybeep_exit);
MODULE_LICENSE("GPL");
-- main.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
while(1)
{
int fd = open("/dev/xydbeep",O_RDWR);//
sleep(1);
close(fd);
sleep(1);
}
return 0;
}
-- 效果图
-- 可以看出自动生成了设备文件
-- 执行main,蜂鸣器响