一、字符设备驱动内核工作实现
1.1 框图

1.2 分配对象
头文件:#include <slab.h> #include <cdev.h>
入口:insmod执行
struct cdev *cdev_alloc(void)
函数功能:分配struct cdev结构体指针
参数:无
返回值:成功返回struct cdev结构体指针,失败返回NULL
出口:
void kfree(const void *block)
函数功能:释放分配到的空间
1.3 初始化对象
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
函数功能:对struct cdev 结构体指针进行初始化
参数:
cdev:分配成功之后结构体指针
fops:操作方法结构体
返回值:无
1.4 注册设备号
静态指定设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
函数功能:静态指定设备号值,需要注意不能和/proc/device目录下设备号重复
参数:9
from:设备号
MKDEV(主设备号, 次设备号) ====> 功能:将主设备号和次设备号合成设备号
MAJOR(dev) ====> 功能:根据设备号的值,得到主设备号值
MINOR(dev) ====> 功能:根据设备号的值,得到次设备号值
count:设备的个数
name:设备名字
返回值:成功返回0,失败返回-1,置位错误码
动态分配设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
函数功能:动态分配设备号值
参数:
dev:分配到的设备号
baseminor:次设备号的起始值
count:设备的个数
name:设备名字
返回值:成功返回0,失败返回-1,置位错误码
注销设备号
void unregister_chrdev_region(dev_t from, unsigned count)
函数功能:注销设备号
参数:
from:设备号
count:设备的个数
1.5 注册对象
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
函数功能:注册字符设备驱动
参数:
p:分配成功之后结构体指针
dev:设备号
count:设备的个数
返回值:成功返回0,失败返回-1,置位错误码
1.6 注销对象
void cdev_del(struct cdev *p)
函数功能:注销字符设备驱动
参数:
p:分配成功之后结构体指针
二、编写代码实例
2.1 驱动代码编写
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#if 0
unsigned int major = 500; //定义一个变量存放静态主设备号值
#else
unsigned int major = 0; //定义一个变量存放主设备号值
#endif
unsigned int minor = 0; //定义一个变量存放次设备号值
struct cdev *cdev;
int count = 3;
#define CNAME "myled"
struct class *cls;
struct device* dev;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t myled_read(struct file *file, char __user *ubuf, size_t size, loff_t *loffs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t myled_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loffs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fop = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.release = myled_close,
};
//入口
static int __init demo_init(void)
{
int ret,i;
dev_t devno;
//1.分配对象,就是分配struct cdev结构体指针 cdev_alloc
cdev = cdev_alloc();
if(cdev == NULL){
printk("cdev alloc is error\n");
ret = -ENOMEM;
goto ERR1;
}
//2.初始化对象,就是初始化分配到struct cdev结构体指针 cdev_init
cdev_init(cdev,&fop);
//3.注册设备号
if(major > 0){ //静态指定设备号 register_chrdev_region
ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
if(ret){
printk("register chrdev region is error\n");
ret = -EIO;
goto ERR2;
}
}else if(major == 0){//动态分配设备号 alloc_chrdev_region cat /proc/devices
ret = alloc_chrdev_region(&devno,minor,count,CNAME);
if(ret){
printk("register chrdev region is error\n");
ret = -EIO;
goto ERR2;
}
major = MAJOR(devno);//根据设备号,获取到主设备号
minor = MINOR(devno);//根据设备号,获取到次设备号
}
//4.注册对象,就是注册字符设备驱动 cdev_add
ret = cdev_add(cdev,MKDEV(major,minor),count);
if(ret){
printk("cdev add is error\n");
ret = -EIO;
goto ERR3;
}
//5.向上层提交目录信息 class_create /sys/class
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create is error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
//6.向上层提交设备节点信息,提交三个设备节点信息 /dev/myled0 /dev/myled1 /dev/myled2 device_create
for(i=0;i<count;i++)
{
dev = device_create(cls,NULL,MKDEV(major,i),NULL,"myled%d",i);
if(IS_ERR(dev)){
printk("device create is error\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 demo_exit(void)
{
int i = 0;
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(demo_init);//指定入口地址
module_exit(demo_exit);//指定出口地址
MODULE_LICENSE("GPL");//许可证
2.2 应用层代码编写
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd = -1;
char buf[128] = {0};
//打开设备文件
fd = open("/dev/myled0",O_RDWR);
if(fd < 0){
perror("open is error");
exit(1);
}
write(fd,buf,sizeof(buf));//写函数
read(fd,buf,sizeof(buf));//读函数
close(fd);//关闭函数
return 0;
}
2.3 测试步骤
1、保证编译驱动成功,安装驱动
2、查看目录信息
linux@ubuntu:~/DC23021/08_step_chrdev$ sudo insmod demo.ko
linux@ubuntu:~/DC23021/08_step_chrdev$ cat /proc/devices
成功现象:
236 myled
3、查看创建设备节点信息
linux@ubuntu:~/DC23021/08_step_chrdev$ cd /sys/class/myled/
linux@ubuntu:/sys/class/myled$ ls
myled0 myled1 myled2
linux@ubuntu:/sys/class/myled$ ls /dev/myled* -ll
crw-r--r-- 1 root root 236, 0 Nov 2 09:20 /dev/myled
crw------- 1 root root 236, 0 Nov 2 13:58 /dev/myled0
crw------- 1 root root 236, 1 Nov 2 13:58 /dev/myled1
crw------- 1 root root 236, 2 Nov 2 13:58 /dev/myled2
4、测试驱动程序
linux@ubuntu:~/DC23021/08_step_chrdev$ gcc test.c
linux@ubuntu:~/DC23021/08_step_chrdev$ sudo ./a.out
linux@ubuntu:~/DC23021/08_step_chrdev$ dmesg
[18025.669546] /home/linux/DC23021/08_step_chrdev/demo.c:myled_open:22
[18025.669553] /home/linux/DC23021/08_step_chrdev/demo.c:myled_write:34
[18025.669554] /home/linux/DC23021/08_step_chrdev/demo.c:myled_read:28
[18025.669556] /home/linux/DC23021/08_step_chrdev/demo.c:myled_close:40
5、卸载驱动
linux@ubuntu:~/DC23021/08_step_chrdev$ sudo rmmod demo
linux@ubuntu:~/DC23021/08_step_chrdev$ ls /dev/myled* -ll
crw-r--r-- 1 root root 236, 0 Nov 2 09:20 /dev/myled
成功现象:没有之前创建的设备节点/dev/myled0,/dev/myled1,/dev/myled2