一.字符设备类型:
dev_t: 定义于include/linux/types.h中,下面是摘自types.h:
typedef __u32 __kernel_dev_t;
typedef __kernel_fd_set fd_set;
typedef __kernel_dev_t dev_t;
typedef __kernel_ino_t ino_t;
typedef __kernel_mode_t mode_t;
typedef __kernel_nlink_t nlink_t;
typedef __kernel_off_t off_t;
typedef __kernel_pid_t pid_t;
.....
可以看出其中dev_t和pid_t都是32为的无符号数.
dev_t dev=MKDEV(ma,mi);
MKDEV(ma,mi)和相关函数定义在include/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))
.....
由此可以看出字符设备的主设备号是12为位的无符号整型数,次设备号是20位的无符号整型数.这里面的转换在源代码里看应该是十分清楚的了.
struct char_device_struct:内核中以这个数据结构来表示一个字符设备.
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major; //主设备号
unsigned int baseminor;//词设备号的起始号.
int minorct;
char name[64]; //设备名
struct file_operations *fops; //在词设备上的操作
struct cdev *cdev; //定义在include/linux/cdev.h
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
struct cdev:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
二.设备号的申请
字符设备设备号的申请有两种方式:(这里是说主设备号)
1.指定申请:就是说你事先找到一个未使用的字符设备号,然后再向内核申请这个指定的设备号.
这中方式的申请函数是:register_chrdev_region(),这个函数定义在
fs/char_dev.c中,下面是内核源码:
/**
* register_chrdev_region() - register a range of device numbers
* @from: 要申请的第一个设备的设备号,必须要有主设备号
* @count: 要申请的有连续设备号的设备的个数
* @name: 设备名称
* 返回0表示申请成功,返回负数说明申请失败
*/
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);
//这个函数在alloc_chrdev_region中也调用了,具体解释见下面解释1.
if (IS_ERR(cd)) goto fail;见解释2.
}
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);见解释3.
}
解释1:__register_chrdev_region//注册主设备号和次设备号
/*
major == 0此函数动态分配主设备号
major > 0 则是申请分配指定的主设备号
返回0表示申请成功,返回负数说明申请失败
*/
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0; int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
//kzalloc分配内存并且全部初始化为0,见include/linux/lab.h
kzalloc其实是 kmalloc(size, flags | __GFP_ZERO);的封装
if (cd == NULL) return ERR_PTR(-ENOMEM);
//include/asm-generic/errno-base.h #define ENOMEM 12 mutex_lock(&chrdevs_lock);
if (major == 0) { //下面是动态申请主设备号
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i—) {
// ARRAY_SIZE(arr)是://(sizeof(arr)/sizeof((arr)[0])+__must_be_array(arr)
// #define __must_be_array(a) //BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))
//__builtin_types_compatible_p gcc内嵌函数用于判断一个变量的类型是
//否为某指定的类型,假如是就返回1,否则返回0。
//#define BUILD_BUG_ON_ZERO(e) (sizeof(char[1 - 2 * !!(e)]) - 1)
//chrdevs是内核中所有已经注册了设备号的设备的一个数组
if (chrdevs[i] == NULL) break;
}
if (i == 0) { ret = -EBUSY; goto out;}
major = i; ret = major;//这里取到一个未使用的设备号
}
cd->major = major; cd->baseminor = baseminor;
cd->minorct = minorct; strncpy(cd->name,name, 64);
//以上是申请到设备后对设备数据结构的填充
i = major_to_index(major); //将其转换为哈希表索引
//return major % CHRDEV_MAJOR_HASH_SIZE;
//#define CHRDEV_MAJOR_HASH_SIZE 255
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
//在字符设备数组中寻找现在注册的设备.
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);//在对这样的数组进行遍历的时候应该加锁的.
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
解释2:
static inline long IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
S_ERR_VALUE:
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
MAX_ERRNO:这里可以看出它的值是0x80000fff
#define MAX_ERRNO 4095 //系统内的错误状态的最大值
解释3:
static inline long PTR_ERR(const void *ptr)
{
return (long) ptr;
}
2.内核自动分配:也就是事先你并不知道哪一个设备号未使用,在申请时就须内核为你挑选一个未使用的设备号.
这中方式的申请函数是:alloc_chrdev_region(),这个函数定义在
fs/char_dev.c中,下面是内核源码:
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: 要申请的有连续设备号的第一个设备的次设备号
* @count: 要申请的有连续设备号的设备的个数
* @name: 设备或驱动的名称 *
* 动态申请设备号,申请成功通常将第一个设备的设备号(包括主,次设备号)
*传递给@dev,并且函数返回0,失败则函数返回负数
*/
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;
}
这里有了上面的分析,理解上面这一段程序应该不是什么大问题了.
驱动
最新推荐文章于 2022-06-15 11:47:35 发布