字符设备驱动框架
注册字符设备
批量注册设备
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
功能:注册字符设备驱动
参数:
@major:主设备号 (一次申请了256个设备,次设备号的范围是0-255)
major > 0 :静态指定设备号
major = 0 :让系统自动申请
@name:驱动的名字
cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
| |
主设备号 名字
·@fops:操作方法结构体
struct file_operations {
int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*release) (struct inode *, struct file *);
}
返回值:
major > 0 成功返回0,失败返回错误码
major = 0 成功返回主设备号,失败返回错误码
void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备
参数:
@major:主设备号
@name:名字
返回值: 无
register_chardev 一次申请256个次设备号,由于设备号的数量有限,所以在很多需要大量设备区驱动的时候就不能使用这个函数。
分步注册设备
更多的时候是用多少申请多少,避免无设备号可用
#include <linux/cdev.h>
#include <linux/slab.h>
1.分配对象
struct cdev {
struct module *owner; //THIS_MODULE
const struct file_operations *ops; //操作方法结构体
struct list_head list; //构成链表
dev_t dev; //设备号
unsigned int count; //设备的个数
};
方法1:
struct cdev cdev; //定义结构体变量
方法2:
struct cdev *cdev: //定义结构体指针
2.对象初始的函数
struct cdev *cdev_alloc(void)
功能:为cdev的结构体指针分配内存
参数:
@无
返回值:成功返回结构体指针,失败返回NULL
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:初始化cdev
参数:
@cdev:cdev的结构体指针
@fops:操作方法结构体
返回值:无
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:静态指定设备号
参数:
@from:设备号的起始的值
@count:设备的个数
@name:设备的名字,可以通过cat /proc/devices
返回值:成功返回0,失败返回错误码
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
功能:动态申请设备号
参数:
@dev:申请到的设备号
@baseminor:次设备号的起始值
@count:设备的个数
@name:设备的名字,可以通过cat /proc/devices
返回值:成功返回0,失败返回错误码
3.注册
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:字符设备驱动的注册
参数:
@p:cdev的结构体指针
@dev:设备号
@count:设备的个数
返回值:成功返回0,失败返回错误码
------------------------------------------------------------------------------------
4.注销
void cdev_del(struct cdev *p)
功能:注销字符设备驱动
参数:
@p:cdev的结构体指针
返回值:无
void unregister_chrdev_region(dev_t from, unsigned count)
功能:归还设备号
参数:
@from:设备号起始的值
@count:个数
返回值:无
void kfree(void *p)
功能:释放cdev_alloc申请的内存
参数:
@p:cdev的结构体指针
返回值:无
创建设备节点
使用shell命令创建设备节点
sudo mknod /dev/mycdev c 240 0
mknod :创建设备节点的命令
/dev/mycdev:创建的设备节点的路径及名字(路径是任意的,习惯上放在/dev/)
c/b : c 字符设备 b 块设备
240 : 主设备号 (驱动分配的主设备号)
0 : 次设备号 {0-255}
这么做每次都要手动创建设备节点,比较麻烦
自动创建设备节点
#include <linux/device.h>
struct class * class_create(owner, name);
功能:向上提交目录名
参数:
@owner:THIS_MODULE //(编译器使用的一个宏)
@name:目录名
返回值:成功返回结构体指针,失败返回错误码指针
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
功能:向上提交节点信息
参数:
@class :class的结构体指针
@parent:一般写为NULL
@devt :设备号
MKDEV(major,minor); //根据主次设备号合成设备号
MAJOR(dev) //根据设备号获取到主设备号
MINOR(dev) //根据设备号得到次设备号
@drvdata:NULL
@fmt,...:设备节点的名字 "myled%d",i
返回值:成功返回结构体指针,失败返回错误码指针
------------------------------------------------------------------------------------
void device_destroy(struct class *class, dev_t devt)
功能:销毁设备节点信息
参数:
@class :class的结构体指针
@devt :设备号
返回值:无
void class_destroy(struct class *cls)
功能:销毁向上提交的目录信息
参数:
@class :class的结构体指针
返回值:无
例子
使用分步注册设备驱动,自动创建设备节点,的读写实例
驱动 rw.c
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#define CNAME "rw"
dev_t major;
dev_t minor;
struct cdev *cdev;
struct device *dev;
struct class *cls;
int count = 3;
char kbuf[128];
ssize_t myread (struct file * file, char __user * ubuf, size_t size, loff_t * offs)
{
printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
if(size > 127)
size = 127;
copy_to_user(ubuf, kbuf, size);
return 0;
}
ssize_t mywrite (struct file * file, const char __user * ubuf, size_t size, loff_t * offs)
{
printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
if(size > 127)
size = 127;
copy_from_user(kbuf, ubuf, size);
return 0;
}
int myclose (struct inode * ino, struct file * file)
{
printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
return 0;
}
int myopen (struct inode * ino, struct file * file)
{
printk("%s:%s:%d\r\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = myopen,
.release = myclose,
.read = myread,
.write = mywrite,
};
static int __init rw_init(void)
{
dev_t devno;
int ret = 0, i;
//1. 分配对象
cdev = cdev_alloc();
if(cdev == NULL)
{
printk("alloc cdev error\r\n");
ret = -ENOMEM;
goto ERR1;
}
//2. 对象初始化
cdev_init(cdev, &fops);
//3. 申请设备号,使用动态申请
ret = alloc_chrdev_region(&devno, minor, count, CNAME);
if(ret)
{
printk("request devno error\r\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
//4. 注册
ret = cdev_add(cdev, MKDEV(major, minor), count);
if(ret)
{
printk("register chardev error\r\n");
goto ERR3;
}
//5. 自动创建节点
cls = class_create(THIS_MODULE, CNAME);
if(IS_ERR(cls))
{
printk("Create class error\r\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for(i = 0; i < count; i++)
{
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "rw%d", i);
if(IS_ERR(dev))
{
printk("device create error \r\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
return 0;
ERR5:
for(--i; i >= 0; i--)
{
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit rw_exit(void)
{
int i;
for (i = 0; i < count; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), count);
kfree(cdev);
}
module_init(rw_init);
module_exit(rw_exit);
MODULE_LICENSE("GPL");
Makefile
使用的是柚木派-v851s
# 设置模块名
modname?=demo
# 设置交叉编译器
CROSS_COMPILE:=/home/linux/tina-v853-open/tina-v853-open/prebuilt/rootfsbuilt/arm/toolchain-sunxi-musl-gcc-830/toolchain/bin/arm-openwrt-linux-
# 设置内核源码路径
KERNELDIR:=/home/linux/tina-v853-open/tina-v853-open/kernel/linux-4.9
# 模块需要的源文件列表
obj-m := $(modname).o
build: kernel_modules
kernel_modules:
make ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
# 清理编译结果
clean:
make -C $(KERNELDIR) M=$(PWD) clean
编译命令为:
make modname=rw
测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char* argv[])
{
int fd;
char buf[128] = { 0 };
if ((fd = open("/dev/rw1", O_RDWR)) == -1) //使用了rw节点,驱动创建了rw0-2三个节点
printf("open error");
printf("input > \r\n");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd, buf, sizeof(buf));
memset(buf,0,sizeof(buf));
read(fd, buf, sizeof(buf));
printf("buf = %s\n",buf);
close(fd);
return 0;
}
运行截图
ar buf[128] = { 0 };
if ((fd = open(“/dev/rw1”, O_RDWR)) == -1) //使用了rw节点,驱动创建了rw0-2三个节点
printf(“open error”);
printf("input > \r\n");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd, buf, sizeof(buf));
memset(buf,0,sizeof(buf));
read(fd, buf, sizeof(buf));
printf("buf = %s\n",buf);
close(fd);
return 0;
}
### 运行截图
