linux驱动开发LDD(四)——字符设备驱动编程框架

(一)概念

  1. 字符设备
    Linux内核中将设备按照硬件操作特性分为三类:
    1)字符设备 :读写时以字节为单位 读写顺序固定
    例如 键盘 鼠标 触摸屏 …

    2)块设备: 读写时以扇区(多字节)为单位 读写顺序不固定
    例如 硬盘 flash …

    3)网络设备: 读写时以帧(多字节)为单位 读写顺序固定
     
    在linux系统中实现一个字符设备硬件驱动:例如 键盘,实则就是在内核中实例化一个struct cdev类型的对象

    struct cdev
    {
       //设备号
       dev_t dev;
       const struct file_operations *ops;
       ... 
    }
    
  2. 设备号
    设备号的数据类型: dev_t 32位系统中就是unsigned int
    设备号 = 主设备号(高12bit) + 次设备号(低20bit)
    主设备号, 用于区分不同类型的设备
    次设备号, 用于区分同一类设备中的不同个体
     
    设备号的注册方式:
    1)静态注册
    选一个未被使用的主设备:0~255
    如何找到哪些主设备号已经被占用:
    cat /proc/devices

    从中选择一个未被使用的主设备号为我所用

    注册设备号:
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    from,要注册的起始设备号
    count, 要连续注册的个数
    name, 名称,注册成功后可cat /proc/devices看到对应主设备号的名称
    注销设备号:
    void unregister_chrdev_region(dev_t from, unsigned count)
    2)动态注册
    由内核分配一个未被使用的主设备号为我所用
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
    dev,传出参数 用于返回被注册成功的第一个设备号
    baseminor,起始次设备号
    count, 连续注册的设备号个数
    注销:unregister_chrdev_region

(二)API

  1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
    //给struct cdev.ops 操作函数集合赋值
    cdev->ops = fops;
    }

  2. //注册cdev
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
    //给struct cdev.dev赋值设备号
    p->dev = dev;

    }

  3. void cdev_del(struct cdev *p)//注销cdev

  4. MKDEV//宏,将主设备号和次设备号组合, 如dev = MKDEV(major, minor)<---->dev = major << 20 | minor

  5. MAJOR和MINOR//宏,从设备号中提取主设备号/次设备号

(三)代码


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");

unsigned int major = 0;
unsigned int minor = 10;
//设备号
dev_t dev = 0;

/*定义一个cdev变量*/
struct cdev led_cdev;

int led_open(struct inode *inode, struct file *filp)
{
    printk("<1>" "enter %s\n", __func__);
    return 0;
}
int led_release(struct inode *inode, struct file *filp)
{
    printk("<1>" "enter %s\n", __func__);
    return 0;
}

struct file_operations led_fops=
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
};

static int __init led_drv_init(void)
{
    if(major)
    {
        //dev = major<<20 | minor;
        dev = MKDEV(major, minor);
        register_chrdev_region(dev, 1, "myleds");
    }
    else
    {
        alloc_chrdev_region(&dev, minor, 1, "myleds");

        major = MAJOR(dev);
        minor = MINOR(dev);
        printk("<1>" "major=%d minor=%d\n",major, minor);
    }
    /*初始化cdev*/
    cdev_init(&led_cdev, &led_fops);
    /*注册cdev*/
    /*
     *led_cdev和申请得到的设备号发生关联
     * */
    cdev_add(&led_cdev, dev, 1);

    return 0;
}

static void __exit led_drv_exit(void)
{
    /*注销 cdev*/
    cdev_del(&led_cdev);

    unregister_chrdev_region(dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);

测试代码
test.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int fd = 0;

    fd = open("/dev/myleds", O_RDONLY);
    if(fd < 0)
    {
        perror("open failed!");
        return -1;
    }
    printf("open successed.....\n");

    sleep(10);

    printf("is closing the device ....\n");
    close(fd);

    return 0;
}

执行效果:

  1. 安装模块时显示主设备号和从设备号
  2. 安装完模块之后,手动创建设备文件mknod /dev/xxx 主设备号 次设备号(其中,xxx任意,相当于给设备起名字,但是测试程序中打开的设备,一定要是这个设备,测试程序中写的myleds,所以这里xxx就是myleds)
  3. 执行测试程序,会发现内核中的led_open函数被调用,然后10秒后led_release被调用。

总结:如linux驱动开发LDD(三)中所记录,系统调用最终会调用到该类设备或者该类文件中对应的函数。在本例中,用户空间调用open函数,最终会在内核态执行led_open,在用户空间执行close,会在内核态执行led_release。只要把相关的硬件驱动代码放入相关的函数中,就可以实现硬件的驱动了。

(四) 设备文件的自动创建

必备条件:

  1. 根文件系统中存在可以执行成mdev

  2. 挂载了 procfs sysfs两个文件系统

  3. 配置了热插拔事件
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    热插拔:/sys目录下文件的变化 (变多 或者变少)

  4. 编程 安装 卸载模块时产生热插拔事件,四个函数:
    1)class_create,宏
    owner,THIS_MODULE
    name, 在/sys/class下创建一个新的类别

    #define class_create(owner, name)		\
    ({						\
    	static struct lock_class_key __key;	\
    	__class_create(owner, name, &__key);	\
    })
    

    2)struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, …)
    class,创建的devic属于哪个类
    parent,父设备,可以为NULL
    devt, 设备号
    drvdata,创建设备时传递的私有数据,可以为NULL
    fmt, …: 可变参数,用来控制设备名。类似printf的fmt,…

    3)void device_destroy(struct class *class, dev_t devt)

    4)void class_destroy(struct class *cls)

    关于自动创建设备文件的代码,见下一章节

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值