字符设备驱动程序
字符设备通过字符(一个接一个的字符)以流方式向用户程序传递数据,就像串行端口那样。字符设备驱动通过/dev目录下的特殊文件公开设备的属性和功能,通过这个文件可以在设备和用户应用程序之间交换数据,也可以通过它来控制实际的物理设备。这也是Linux的基本概念,一切皆文件。字符设备驱动程序是内核源码中最基本的设备驱动程序。
字符设备功能定义
//char_device.c
struct hello_char_dev{ //实际的字符设备结构,类似于面向对象的继承
struct cdev cdev;
char c;
};
struct hello_char_dev *hc_devp;
int hc_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "open hc_dev%d %d\n",iminor(inode),MINOR(inode->i_cdev->dev));
return 0;
}
ssize_t hc_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
printk(KERN_INFO "read hc_dev\n");
return 0;
}
ssize_t hc_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
printk(KERN_INFO "write hc_dev\n");
return count; //不能返回0,否则会不停的写
}
int hc_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "release hc_dev\n");
return 0;
}
struct file_operations hc_fops = { //字符设备的操作函数
.owner = THIS_MODULE,
.read = hc_read,
.write = hc_write,
.open = hc_open,
.release = hc_release,
};
定义设备初始化
static int __init hello_init(void)
{
int ret,i;
printk(KERN_INFO "---BEGIN CHAR LINUX MODULE---\n");
if(hello_major){
devt=MKDEV(hello_major,hello_minor);
ret=register_chrdev_region(devt,hello_nr_devs,"first_char_devie"); //使用指定的设备号分配
}
else{
ret = alloc_chrdev_region(&devt,hello_minor,hello_nr_devs,"first_char_devie");//动态分配主设备号
hello_major = MAJOR(devt);
}
if (ret < 0) {
printk(KERN_WARNING "CHAR DEVICE: can't get major %d\n", hello_major);
goto fail;
}
hc_devp = kzalloc(sizeof(struct hello_char_dev)*hello_nr_devs,GFP_KERNEL); //给字符设备分配空间,这里hello_nr_devs为2
if(!hc_devp)
{
printk(KERN_WARNING "alloc mem failed");
ret = -ENOMEM;
goto failure_kzalloc; //内核常用goto处理错误
}
for(i=0;i<hello_nr_devs;i++){
cdev_init(&hc_devp[i].cdev,&hc_fops); //初始化字符设备结构
hc_devp[i].cdev.owner = THIS_MODULE;
ret = cdev_add(&hc_devp[i].cdev,MKDEV(hello_major,hello_minor+i),1); //添加该字符设备到系统中
if(ret)
{
printk(KERN_WARNING"fail add hc_dev%d",i);
}
}
printk(KERN_INFO "---END CHAR LINUX MODULE---\n");
return 0;
failure_kzalloc:
unregister_chrdev_region(devt,hello_nr_devs);
fail:
return ret; //返回错误,模块无法正常加载
}
定义设备退出
static void __exit hello_exit(void)
{
int i;
for(i=0;i<hello_nr_devs;i++)
cdev_del(&hc_devp[i].cdev);
kfree(hc_devp);
unregister_chrdev_region(devt,hello_nr_devs); //移除模块时释放设备号
printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(hello_init);
module_exit(hello_exit);
描述性定义
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("gyh"); //作者
MODULE_VERSION("V1.0"); //版本
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m :=hello_chr.o
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.mod *.order *.symvers
编译加载 insmod char_device.ko
dmesg日志中包含了设备打印
cat /proc/devices查看设备号,此时设备驱动程序已经开始工作,但是在/dev下系统没有生成默认的文件(此文件可以直接对设备驱动进行操作),需要我们手动创建
1.手动创建设备文件
mknod /dev/c_dev0 c 241 0
mknod /dev/c_dev1 c 241 1
2. 调用class结构体创建设备文件
/*
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,
内核同时提供了class_create(…)函数, 可以用它来创建一个类,这个类存放于sysfs下面,
一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。
这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,
去/sysfs下寻找对应的类从而创建设备节点。
*/
class_create() //创建类 会在/sys/class/下创建文件夹
class_destroy()
device_create() //在我们创建的类下创建设备,有了这步就会在/dev生成设备节点
device_destroy() //在退出时删除设备文件
读取设备文件 cat /dev/c_dev0
写设备 echo 1 > /dev/c_dev0
完整代码
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
//#include <linux/device.h> //cdev里边有
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2
int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t devt; //高12位是主设备号,低20位是次设备号
int hello_nr_devs = HELLO_NR_DEVS;
module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);
struct hello_char_dev{ //实际的字符设备结构,类似于面向对象的继承
struct cdev cdev;
char c;
};
struct hello_char_dev *hc_devp;
struct class *hc_cls;
int hc_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "open hc_dev%d %d\n",iminor(inode),MINOR(inode->i_cdev->dev));
return 0;
}
ssize_t hc_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
printk(KERN_INFO "read hc_dev\n");
return 0;
}
ssize_t hc_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
printk(KERN_INFO "write hc_dev\n");
return count; //不能返回0,否则会不停的写
}
int hc_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "release hc_dev\n");
return 0;
}
struct file_operations hc_fops = { //字符设备的操作函数
.owner = THIS_MODULE,
.read = hc_read,
.write = hc_write,
.open = hc_open,
.release = hc_release,
};
static int __init hello_init(void)
{
int ret,i;
printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\n");
if(hello_major){
devt=MKDEV(hello_major,hello_minor);
ret=register_chrdev_region(devt,hello_nr_devs,"hello_chr"); //使用指定的设备号分配
}
else{
ret = alloc_chrdev_region(&devt,hello_minor,hello_nr_devs,"hello_chr");//动态分配主设备号
hello_major = MAJOR(devt);
}
if (ret < 0) {
printk(KERN_WARNING "hello: can't get major %d\n", hello_major);
goto fail;
}
hc_devp = kzalloc(sizeof(struct hello_char_dev)*hello_nr_devs,GFP_KERNEL); //给字符设备分配空间,这里hello_nr_devs为2
if(!hc_devp)
{
printk(KERN_WARNING "alloc mem failed");
ret = -ENOMEM;
goto failure_kzalloc; //内核常用goto处理错误
}
for(i=0;i<hello_nr_devs;i++){
cdev_init(&hc_devp[i].cdev,&hc_fops); //初始化字符设备结构
hc_devp[i].cdev.owner = THIS_MODULE;
ret = cdev_add(&hc_devp[i].cdev,MKDEV(hello_major,hello_minor+i),1); //添加该字符设备到系统中
if(ret)
{
printk(KERN_WARNING"fail add hc_dev%d",i);
}
}
hc_cls = class_create(THIS_MODULE,"hc_dev");
if(!hc_cls)
{
printk(KERN_WARNING"fail create class");
ret = PTR_ERR(hc_cls);
goto failure_class;
}
for(i=0;i<hello_nr_devs;i++){
device_create(hc_cls,NULL,MKDEV(hello_major,hello_minor+i),NULL,"hc_dev%d",i);
}
printk(KERN_INFO "---END HELLO LINUX MODULE---\n");
return 0;
failure_class:
kfree(hc_devp);
failure_kzalloc:
unregister_chrdev_region(devt,hello_nr_devs);
fail:
return ret; //返回错误,模块无法正常加载
}
static void __exit hello_exit(void)
{
int i;
for(i=0;i<hello_nr_devs;i++)
{
device_destroy(hc_cls,MKDEV(hello_major,hello_minor+i));
}
class_destroy(hc_cls);
for(i=0;i<hello_nr_devs;i++)
cdev_del(&hc_devp[i].cdev);
kfree(hc_devp);
unregister_chrdev_region(devt,hello_nr_devs); //移除模块时释放设备号
printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(hello_init);
module_exit(hello_exit);
//描述性定义
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("gyh"); //作者
MODULE_VERSION("V1.0"); //版本