Linux驱动字符设备(设备号的申请)

在了解Linux字符设备先了解一下Linux设备的分类。

Linux设备分类

Linux设备主要分为字符设备、块设备、网络设备。
字符设备:能够像字节流一样被访问且没有缓冲是按顺序访问的设备,当对字符设备发出读写请求,相应的IO操作立即发生。Linux系统中很多设备都是字符设备,绝大多数的设备都是字符设备。比如LED、按键、键盘、串口、传感器、LCD。字符设备驱动通过字符设备文件来访问。
块设备:按照数据块来访问且有缓冲是具有随机访问能力的设备,比如访问硬盘就不是一个字节一个字节的访问,而是直接访问数据块,数据块的大小是固定的但是不同的系统不一样。比如内存、磁盘、SD卡、U盘。块设备驱动通过块设备文件来访问。
网络设备:网络设备由Linux的网络子系统驱动,负责数据包的发送和接收,而不是面向流设备,因此在Linux系统文件系统中网络设备没有节点。访问网络设备不通过文件,通过套接字(网络通信地址)访问。

本文重点讲字符设备。

字符设备

驱动是沟通硬件和上层应用的媒介,字符设备驱动通过字符设备文件来访问,访问设备文件使用文件IO,在用户层访问设备文件和普通文件的方法是没有区别。
那如何找到设备文件相对应的驱动呢?
Linux中所有的设备文件都在/dev目录下,而通过设备号就可以将设备文件和驱动联系起来。

设备号

设备号分为主设备号和次设备号。其中主设备号是用来区分不同类别的设备,而次设备号用来区分一类设备中的不同个体。例如输入设备和输出设备是不同的主设备号,但是输入设备不只一种,像鼠标和键盘都是输入设备就要使用次设备号来区分。
可以在/proc/devices文件中查询哪些主设备号以及被使用了。
字符设备:
在这里插入图片描述
块设备:
在这里插入图片描述

设备号数据类型:dev_t原型是一个32位无符号整形类型的值(unsigned int ),其中高12位表示主设备号,低20位表示次设备号。

内核中提供了操作设备号的宏

MAJOR(设备号);//通过设备号获取主设备号
MINOR(设备号);//通过设备号获取次设备号
MKDEV(主设备号,次设备号);//通过主设备号和次设备号构造设备号

一起看看宏的原型

#define MINORBITS   20
#define MINORMASK   ((1U << MINORBITS) - 1)

#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))                                                                            
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

获取主设备号的宏MAJOR是通过将设备号右移20位留下高12位的主设备号。
获取次设备号的宏MINOR是通过将1左移20位(1U表示无符号整型1,C语言默认常数是有符号整型)再减一就变成了前12位为0,后20位为1了。然后与上设备号就获得了低20位的次设备号。
获取设备号的宏MKDEV是通过将主设备左移20位再或上次设备号就得到了完整的设备号。
设备号在内存中属于资源(虽然有很多,估计也用不完,但毕竟是有限的且不同共用),所以需要使用设备号就要向系统申请。

申请设备号

首先需要加上两个头文件。

#include <linux/cdev.h>
#include <linux/fs.h>

系统提供了两种申请方法:静态申请和动态申请。
静态申请

  • 选择一个内核中未被使用的主设备号(在/proc/devices中查看)。
  • 根据设备个数分配次设备号,一般从0开始。
  • 使用宏构造完整的设备号。
  • 调用函数register_chrdev_region向系统申请。
  • 不再使用设备号需要注销,通过函数unregister_chrdev_region实现注销。

int register_chrdev_region(dev_t from, unsigned count, const char *name);
参数:
from - 要申请的起始设备号
count - 设备号个数
name - 设备号在内核中对应的名称
返回0表示成功,返回非0表示失败

void unregister_chrdev_region(dev_t from, unsigned count);
参数:
from - 要注销的起始设备号
count - 设备号个数

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

//主设备号
#define CDD_MAJOR 220
//起始次设备号
#define CDD_MINOR 0
//设备号个数
#define CDD_COUNT 1

//设备号
dev_t dev;

//加载函数
int cdd_init(void)
{
	int ret;

	//构造设备号
	dev = MKDEV(CDD_MAJOR, CDD_MINOR);

	// 1.静态申请设备号
	ret = register_chrdev_region(dev, CDD_COUNT, "cdd_demo");
	if(ret<0){
		printk("register_chrdev_region failed!\n");
		return ret;
	}

	printk("register_chrdev_region success!\n");

	return 0;
}

//卸载函数
void cdd_exit(void)
{
	//注销设备号
	unregister_chrdev_region(dev, CDD_COUNT);
}

//声明为模块的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);

MODULE_LICENSE("GPL");//GPL模块许可证

在这里插入图片描述

在这里插入图片描述

动态申请
就是向系统申请一个设备号,系统自动分配一个没有使用过的设备号。
使用函数alloc_chrdev_region向系统申请。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
参数:
dev - 设备号的地址
baseminor - 起始次设备号
count - 设备号个数
name - 设备号在内核中对应的名称
返回0表示成功,返回非0表示失败

注销使用的也是函数register_chrdev_region。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

//起始次设备号
#define CDD_MINOR 0
//设备号个数
#define CDD_COUNT 1

//设备号
dev_t dev;

//加载函数
int cdd_init(void)
{
	int ret;

	// 2.动态态申请设备号
	ret = alloc_chrdev_region(&dev, CDD_MINOR, CDD_COUNT, "cdd_demo");
	if(ret<0){
		printk("register_chrdev_region failed!\n");
		return ret;
	}

	printk("register_chrdev_region success!\n");
	printk("major number:%d\n",MAJOR(dev));
	
	return 0;
}

//卸载函数
void cdd_exit(void)
{
	//注销设备号
	unregister_chrdev_region(dev, CDD_COUNT);
}

//声明为模块的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);

MODULE_LICENSE("GPL");//GPL模块许可证

在这里插入图片描述

在这里插入图片描述
好了,最后如果有什么说的不对的地方欢迎在评论区指正。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值