正点原子-驱动开发-字符设备驱动

Linux中的三大类驱动:字符设备、块和网络设备驱动

I2C、SPI、音频等都属于字符设备驱动 的类型

EMMC、NAND、SD卡和 U盘等存储都属于块设备

网卡,WIFI等都属于网络驱动

一个设备可以属于多种设备驱动,如USB WIFI,其USB接口属于字符设备驱动,但WIFI功能同时属于网络驱动。

字符设备驱动模型

应用程序运行在用户空间,而Linux 驱动属于内核的一部分,当在用户空间使用open()函数打开/dev/eth0这个驱动设备文件时,必须使用系统调用的方法来实现,open,close,write,read这些函数都是由C库提供的具有系统调用功能的函数。

 Linux内核文件 include/linux/fs.h中 ,有个叫做 file_operations的结构体,就是 Linux内核驱动操作函数集合

linux驱动模块的加载和卸载

两种方式:直接编译进内核,或者编译成模块.ko文件,然后使用insmod或者modprobe加载,使用rmmod或者modprobe -r卸载

# insmod drv.ko
# rmmod drv.ko


# modprobe drv.ko
# modprobe -r drv.lo

// 建议使用modprobe drv.ko来加载驱动模块,使用rmmod来卸载模块
// modprobe 会根据依赖关系加载相关模块,而insmod只加载指定的模块
// modprobe -r也会根据依赖关系卸载相关模块,rmmod只卸载指定模块

//驱动入口函数
static int __init xxx_init(void)
{
    return 0;
}

//驱动出口函数
static void __exit xxx_exit(void)
{

}

//将上面两个函数指定为出口函数和出口函数
module_init(xxx_init);
module_exit(xxx_exit);

字符设备注册和注销

当模块加载成功以后需要注册字符设备,卸载后也需要注销字符设备

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) 
// major为主设备号, name 为设备名, fops 为操作函数集合


static inline void unregister_chrdev(unsigned int major, const char *name)

一般字符设备的注册在驱动模块入口函数 xxx_init 中进行,注销在出口函数 xxx_exit中进行

static struct file_operations test_fops;

//驱动入口函数
static int __init xxx_init(void)
{
    retvalue = register_chrdev(200, "chrtest", &test_fops);    
    if(retvalue < 0)
        {
        }
    return 0;
}

//驱动出口函数
static void __exit xxx_exit(void)
{
    unregister_chrdev(200, "chrtest");
}

//将上面两个函数指定为出口函数和出口函数
module_init(xxx_init);
module_exit(xxx_exit);

通过cat /proc/devices命令查看当前已经被使用的主设备号。

初始化 fops结构体操作函数集合

static int chrtest_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static int chrtest_release(struct inode *inode, struct file *filp)
{
    return 0;
}



static struct file_operations test_fops =
{
    .owner = THIS_MODULE,
    .open = chrtest_open,
    .read = chrtest_read,
    .write = chrtest_write,
    .release = chrtest_release,
};

//驱动入口函数
static int __init xxx_init(void)
{
    retvalue = register_chrdev(200, "chrtest", &test_fops);    
    if(retvalue < 0)
        {
        }
    return 0;
}

//驱动出口函数
static void __exit xxx_exit(void)
{
    unregister_chrdev(200, "chrtest");
}

//将上面两个函数指定为出口函数和出口函数
module_init(xxx_init);
module_exit(xxx_exit);

//添加license信息和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jack");

添加LICENSE和作者信息

license信息必须添加,否则编译会报错,如上图。

Linux设备号

Linux中每个设备都有一号,由主和次两部分组成,主设备号(major)表示某一个具体的驱动,此设备号(minor)表示使用这个驱动的各个设备。

Linux提供一个数据结构dev_t表示设备号,定义在include/linux/types.h中。

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
typedef unsigned int __u32;
// 因此,dev_t就是unsigned int类型,32位的数据类型

设备号位32位,高12位为主设备号,低20位为此设备号。

因此,主设备号范围为0~4095

在 include/linux/kdev_t.h中定义了几个宏定义的设备号的函数。

#define MINORBITS 20
// MINORBITS表示次设备号位数,一共是20

#define MINORMASK ((1U << MINORBITS) - 1)
// MINORMASK表示次设备号掩码。

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
// MAJOR用于从 dev_t中获取主设备号,将 dev_t右移 20位即可

#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
// MINOR用于从dev_t中获取次设备号,取 dev_t的低 20位的值即可

#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
// MKDEV用于将给定的主设备号和次值组合成 dev_t类型的设备号

动态分配设备号

静态手动申请设备号,容易带来冲突问题。

Linux推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动生成一个不冲突的设备号。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

// dev:保存申请到的设备号。
// baseminor:此设备号起始地址
// count:要申请的设备号数量
// name: 设备名字

void unregister_chrdev_region(dev_t from, unsigned count)

// from:要释放的设备号
// count:表示从from开始,要释放的设备号数量

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值