您的位置:首页 > 运维架构 > Linux

Linux设备驱动之字符设备(一)

2016-06-22 15:27 429 查看

Linux中设备驱动的分类



从上图可以看到Linux系统将各异的设备分为三大类:字符设备,块设备和网络设备。内核针对每一类设备都提供了对应驱动模型架构,包括基本的内核设施和文件系统接口。

字符设备:在传送过程中以字符为单位,一个字节一个字节的读写,不能随机的读写数据,因为这类设备读写速度比较缓慢(因而其内核设施中不提供缓存机制),常见的字符设备有键盘,鼠标已打印机设备等。

块设备: 是指可以从任意位置读取数据的设备,对这种设备读写是按块为单位读写的。它使用缓存区来暂存数据,等待条件成熟后,会一并将数据写入到设备或者从设备一次性读出到缓冲区。比较U盘,磁盘,SD卡等。

网络设备: 网络设备不同于字符设备和块设备,它是面向报文的。同时在/dev目录下没有设备节点这样一说,在应用层是用户是通过API的socket函数来使用网络设备的。比如网卡等。

设备号的构成

主设备号与次设备号

关于设备号,我们先通过如下的图来了解一下



从上图可以看出,c代表的是字符设备,d代码的是块设备。

对于红色区域来说,1是主设备号,11是次设备号。

对于绿色区域来说,7是主设备号,0-7代表是次设备号。

主设备号用来标识对于的设备驱动程序,而次设备号则由驱动程序使用,用来标识它所管理的若干同类设备。

设备号的表示

在linux系统中,设备号用dev_t表示。这是个32位的无符号整数。

<inclue/linux/types.h>
---------------------------
typedef __kernel_dev_t      dev_t;
typedef __u32 __kernel_dev_t;


在内核中,dev_t的低20位用来表示次设备号,高12位用来表示主设备号。随着Linux系统的演变,上述的主次设备号的分发可能在将来会发生变化,所以设备驱动程序开发者应该避免直接使用主次设备号所占的位宽来获得对于的主设备号或次设备号。为了保证以后主次设备号所占的位数发生变化之后,驱动程序依然可以正常工作,内核提供了如下了几个宏来操作设备号。

<inclue/linux/kdev_t.h>
-------------------------
#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宏用来从一个dev_t类型的设备号中提取出主设备号,MINOR宏从来从一个dev_t类型的设备号中提取出此设备号。MKDEV则是将主设备号ma和次设备号mi合成一个dev_t类型的设备号。

假设在内核版本之后对主次设备号所占的位数发生了变化,MINORBITS修改为18位,只要驱动是使用MAJOR和MINOR宏来操作设备号,就不需要修改驱动代码也可以在新内核中使用。

设备号的分配

在内核源码中,设备号的分配主要有两个函数:

静态分配设备号

<fs/char_dev.c>
----------------------------------------------------------
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
*        the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;

for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}


该函数是用来在知道主设备号的前提下使用,第一个参数form表示一个设备号,第二个参数count表示次设备的个数,也就是当前驱动程序所管理的同类设备的个数,第三个参数name表示设备或者驱动的名称。成功返回0,失败返回负数。

比如内核代码中使用register_chrdev_region申请设备号示例:

#define INPUT_MAJOR     13
#define INPUT_MAX_CHAR_DEVICES      1024
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}


以上代码申请了主设备号为13,总共存在1024个次设备,设备的名字为input。

动态分配设备号

<fs/char_dev.c>
--------------------------------------------------------------------------------------
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers.  The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev.  Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}


该函数有系统动态的分配设备号,是在主设备号不知情的情况下,让系统给分配设备号。第一个参数dev表示是输出参数,也就是设备号,第二个参数baseminor表示第一个次设备号编号,第三个参数count表示次设备号的个数,第四个参数name也就是设备或者驱动的名称。

内核中使用alloc_chrdev_region的示例:

#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
void __init rtc_dev_init(void)
{
int err;

err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
if (err < 0)
pr_err("failed to allocate char dev region\n");
}


上述示例代码是通过alloc_chrdev_region函数申请设备号,设备的个数为16个,设备的名称为rtc设备。

设备号释放

在驱动程序不使用的时候需要释放设备号,因为设备号也是系统的资源,不用的使用需要及时释放资源。已供其他设备使用。

<fs/char_dev.c>
-----------------------------------------------------------------------------------
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from.  The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
{
dev_t to = from + count;
dev_t n, next;

for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
}


使用该函数可以使用申请的设备号,第一个参数from表示要释放的设备号,第二个参数count表示要释放的个数。

内核使用unregister_chrdev_region的示例:

#define INPUT_MAJOR     13
#define INPUT_MAX_CHAR_DEVICES      1024
static void __exit input_exit(void)
{
unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES);
}


从上面代码可知,如果调用unregister_chrdev_region就会从系统中释放主设备号13的设备。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息