linux驱动学习之alloc_chrdev_region源码分析(二):__register_chrdev_region
/*
major : 主设备号
baseminor : 次设备号
minorct : 次设备号数目
name : 名称(标签)
*/
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;
//在内核中申请内存空间,如果失败返回ERR_PTR(-ENOMEM)错误,ERR_PRT附:A.1
// ENOMEM Kernel memory allocation error
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
/*
当形参中主设备号为0时,分配主设备号方式
*/
if (major == 0) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
//chardevs实现,见附A.2,
if (chrdevs[i] == NULL) //当数组对应的下标元素值为空时,表示可以分配空间
break;
}
if (i == 0) {
//当主设备号都用完时执行如下错误
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
// struct char_device_struct *cd 设置对应的主设备号、次设备号、次设备号个数、名称(标签)
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name)); //函数实现见附A.4
//以下代码主要用于处理当第一个形参不是 0 时,且当major大于CHRDEV_MAJOR_HASH_SIZE时,可能产生的错误
i = major_to_index(major); //用主设备号(major)和 CHRDEV_MAJOR_HASH_SIZE取模,实现代码见附:A.3
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; // 当分配成功,返回cd,即:cd包含:内核分配的主设备号,形参中的次设备号、次设备号数量、名字(标签)
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
附 A.1
//功能实现:include/linux/err.h
static inline void * __must_check ERR_PTR(long error)
{
return (void *) error;
}
附 A.2
//功能实现:fs/char_dev.c
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major; //主设备号
unsigned int baseminor;//次设备号
int minorct;//次设备号数量
char name[64];//名字
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
//宏定义位置:CHRDEV_MAJOR_HASH_SIZE
//linux/fs.h
附A.3
//功能实现:fs/char_dev.c
/* index in the above */
static inline int major_to_index(unsigned major)
{
return major % CHRDEV_MAJOR_HASH_SIZE;
}
附 A.4
函数声明位置:include/linux/string.h
#include <linux/string.h>
/*
des : 字符串的目的位置
src :字符串的源位置
size : 拷贝的字节数
返回值 :没有意义,见实现代友:
*/
size_t strlcpy(char *des, const char *src, size_t size); //用法同strncpy();
//实现代码:
//vim lib/string.c
size_t strlcpy(char *dest, const char *src, size_t size)
{
size_t ret = strlen(src); //获取源字符串大小
if (size) {
//当拷贝数据大于0时执行拷贝
//当源字符串长度大于或等于要拷贝的字符串长度时:len = size -1,否则len = ret
size_t len = (ret >= size) ? size - 1 : ret;
memcpy(dest, src, len);//见附B.1
dest[len] = '\0';//添加字符串结束符,strlen计算时只计算了有效字符,没有计算结束标志'\0'
}
return ret;//无论结果如何,均返回源字符串长度,所以返回值没有意义
}
EXPORT_SYMBOL(strlcpy);
附 B.1
//memcpy(3)
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
//The memcpy() function returns a pointer to dest.