searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

字符驱动程序简单实例

2023-11-16 07:54:01
9
0
字符设备驱动程序
字符设备通过字符(一个接一个的字符)以流方式向用户程序传递数据,就像串行端口那样。字符设备驱动通过/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");     //版本
0条评论
0 / 1000
顾****涵
5文章数
0粉丝数
顾****涵
5 文章 | 0 粉丝
原创

字符驱动程序简单实例

2023-11-16 07:54:01
9
0
字符设备驱动程序
字符设备通过字符(一个接一个的字符)以流方式向用户程序传递数据,就像串行端口那样。字符设备驱动通过/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");     //版本
文章来自个人专栏
系统内核
2 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0