关于设备号的思考



这是最后的运行流程,用户open设备节点,通过主设备号找到驱动程序,根据次设备号找到对应的设备,使用驱动函数中的read(),write()等函数操作设备。

为了搭建这样的运行时环境,需要做如下工作:

1 驱动程序加载(申请主设备号,在/proc/devices下创建设备)

2 根据具体设备创建设备节点 (查询合适的主设备号和可用的次设备号 在/dev下面创建设备节点)

先说说设备号的申请吧

复制代码
int register_chrdev_region(dev_t first, unsigned int -count, char*name)
这个是静态申请 第一个参数是自己定义的一个32位无符号整数,第二个是要申请的连续设备号的个数,第三个参数是设备名称
int alloc_chrdev_region(dev_t *dev, unsigned int -firstminor, unsigned int -count, char *name)

这个是动态申请 dev中存放的是返回值 第二个参数是只第一个次设备号 一般为0,count还是指支持的设备个数,name是设备名

复制代码

比如,若first为 0x3FFFF0,count为0x5,那么该函数就会为5个设备注册设备号,分别是0x3FFFF0、 0x3FFFF1、 0x3FFFF2、 0x3FFFF3、 0x3FFFF4,其中0x3(高12位)为这5个设备所共有的主设备号(也就是说这5个设备都使用同一个驱动程序)。而0xFFFF0、 0xFFFF1、 0xFFFF2、 0xFFFF3、 0xFFFF4就分别是这5个设备的次设备号了。

复制代码
 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;                  // 指向字符设备驱动程序描述符的指针
   } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

注意,内核并不是为每一个字符设备编号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 char_device_struct 结构。
chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。(如果出现相同的主设备号,还可以使用拉链法处理)
不过不管怎么样,上面的这个结构体只与主设备号有关(或者说只与驱动有关,这也是/proc/devices的源头吧)
复制代码
复制代码
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。
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);
   if (cd == NULL)
       return ERR_PTR(-ENOMEM);

   mutex_lock(&chrdevs_lock);

   if (major == 0) {
//主设备号为0就自动分配  从最后向前寻找  找一个主设备号没有被占用的
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--)
           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);

   for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
       if ((*cp)->major > major ||
            ((*cp)->major == major && ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) ))
           break;

 
   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;

     
       if (new_max >= old_min && new_max <= old_max) {
           ret = -EBUSY;
           goto out;
       }

     
       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);
}
复制代码
复制代码
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);
}
register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,
并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。
复制代码
复制代码
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;
}
alloc_chrdev_region() 函数用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。
复制代码

总结上述,申请设备号的最终结果就是创建了一个对应的结构体置于一个链表中。

上述只是一部分,/proc/devices中有内容了,cdev_map链表中也有提供给用户的信息了。可是/dev中还是没有设备节点啊?当然你可以手动创建,可那时虚拟的,不是具体意义上的设备

那么设备的监测到/dev下设备节点的创建又经历了什么过程呢?

当linux系统启动时,/dev目录是空的。udev程序扫描/sys/class目录,查找名字为dev的文件。
每一个这样的dev文件,其内容是系统支持逻辑设备的主次设备号。udev根据主次设备号,在/dev目录
下创建相应的文件。同时,udev会根据配置文件,分配文件名并创建符号链接。最终,对于此系统内核
支持的每一种设备,都会在/dev目录下有相应的设备文件。

struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);   class的名字  /sys/class下的
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);   设备的名字 /dev下的   创建设备节点  不需要手动创建了
这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件

还有一个问题:设备节点的创建都在驱动init中,怎么没有看到设备的具体信息呢?这与驱动的编写有关系,比如总线设备,设备信息与驱动信息就是分开的。字符设备

有很多是写在一起的,可能字符设备的具体信息影响不大。刚做了个测试,我们记得申请主设备号的时候还包括从设备号,有一个从设备号的起始值与范围,其实这是个约束。其实他就对应了具体的设备,所以在自己创建设备节点的时候要注意,你到底要访问那个设备,从设备号是个关键,创建设备节点要与之对应。这也充分说明了字符设备驱动中,设备与驱动是一体的,他没有注册驱动的说法,不像总线设备那样,设备与驱动的分开注册的。

  384人阅读  评论(0)  收藏  举报
  分类:

这是最后的运行流程,用户open设备节点,通过主设备号找到驱动程序,根据次设备号找到对应的设备,使用驱动函数中的read(),write()等函数操作设备。

为了搭建这样的运行时环境,需要做如下工作:

1 驱动程序加载(申请主设备号,在/proc/devices下创建设备)

2 根据具体设备创建设备节点 (查询合适的主设备号和可用的次设备号 在/dev下面创建设备节点)

先说说设备号的申请吧

复制代码
int register_chrdev_region(dev_t first, unsigned int -count, char*name)
这个是静态申请 第一个参数是自己定义的一个32位无符号整数,第二个是要申请的连续设备号的个数,第三个参数是设备名称
int alloc_chrdev_region(dev_t *dev, unsigned int -firstminor, unsigned int -count, char *name)

这个是动态申请 dev中存放的是返回值 第二个参数是只第一个次设备号 一般为0,count还是指支持的设备个数,name是设备名

复制代码

比如,若first为 0x3FFFF0,count为0x5,那么该函数就会为5个设备注册设备号,分别是0x3FFFF0、 0x3FFFF1、 0x3FFFF2、 0x3FFFF3、 0x3FFFF4,其中0x3(高12位)为这5个设备所共有的主设备号(也就是说这5个设备都使用同一个驱动程序)。而0xFFFF0、 0xFFFF1、 0xFFFF2、 0xFFFF3、 0xFFFF4就分别是这5个设备的次设备号了。

复制代码
 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;                  // 指向字符设备驱动程序描述符的指针
   } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

注意,内核并不是为每一个字符设备编号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 char_device_struct 结构。
chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。(如果出现相同的主设备号,还可以使用拉链法处理)
不过不管怎么样,上面的这个结构体只与主设备号有关(或者说只与驱动有关,这也是/proc/devices的源头吧)
复制代码
复制代码
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。
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);
   if (cd == NULL)
       return ERR_PTR(-ENOMEM);

   mutex_lock(&chrdevs_lock);

   if (major == 0) {
//主设备号为0就自动分配  从最后向前寻找  找一个主设备号没有被占用的
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--)
           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);

   for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
       if ((*cp)->major > major ||
            ((*cp)->major == major && ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) ))
           break;

 
   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;

     
       if (new_max >= old_min && new_max <= old_max) {
           ret = -EBUSY;
           goto out;
       }

     
       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);
}
复制代码
复制代码
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);
}
register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,
并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。
复制代码
复制代码
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;
}
alloc_chrdev_region() 函数用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。
复制代码

总结上述,申请设备号的最终结果就是创建了一个对应的结构体置于一个链表中。

上述只是一部分,/proc/devices中有内容了,cdev_map链表中也有提供给用户的信息了。可是/dev中还是没有设备节点啊?当然你可以手动创建,可那时虚拟的,不是具体意义上的设备

那么设备的监测到/dev下设备节点的创建又经历了什么过程呢?

当linux系统启动时,/dev目录是空的。udev程序扫描/sys/class目录,查找名字为dev的文件。
每一个这样的dev文件,其内容是系统支持逻辑设备的主次设备号。udev根据主次设备号,在/dev目录
下创建相应的文件。同时,udev会根据配置文件,分配文件名并创建符号链接。最终,对于此系统内核
支持的每一种设备,都会在/dev目录下有相应的设备文件。

struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);   class的名字  /sys/class下的
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);   设备的名字 /dev下的   创建设备节点  不需要手动创建了
这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件

还有一个问题:设备节点的创建都在驱动init中,怎么没有看到设备的具体信息呢?这与驱动的编写有关系,比如总线设备,设备信息与驱动信息就是分开的。字符设备

有很多是写在一起的,可能字符设备的具体信息影响不大。刚做了个测试,我们记得申请主设备号的时候还包括从设备号,有一个从设备号的起始值与范围,其实这是个约束。其实他就对应了具体的设备,所以在自己创建设备节点的时候要注意,你到底要访问那个设备,从设备号是个关键,创建设备节点要与之对应。这也充分说明了字符设备驱动中,设备与驱动是一体的,他没有注册驱动的说法,不像总线设备那样,设备与驱动的分开注册的。

<think>好的,用户的问题是关于如何恢复或重新生成设备丢失的SN号。首先,我需要明确SN号是什么,通常SN号是设备的唯一序列号,由制造商在生产时写入,用于标识设备。用户可能遇到了设备SN号丢失的情况,需要找回或重新生成。 接下来,我需要考虑不同的设备类型,比如手机、路由器、硬盘等,每种设备的恢复方法可能不同。因此,回答中需要分情况讨论。同时,用户可能没有技术背景,所以步骤要尽量详细,避免使用专业术语。 然后,思考可能的恢复方法。如果设备本身有备份功能,或者SN号存储在某个可访问的位置(如系统设置、标签、包装盒等),用户可以通过这些途径找回。如果SN号因系统问题丢失,可能需要通过厂商工具重新刷入,或者联系客服获取帮助。 另外,引用中的信息需要与问题相关。引用[1]提到的自回归方法可能不直接相关,但引用[2]提到Oracle中的序列号生成机制,可能与设备SN号的生成逻辑有类似之处。引用[3]指出生成长文本时中间信息丢失的问题,这可能类比设备SN号在写入过程中的问题,但需要谨慎关联。 需要确保回答结构清晰,分步骤说明,并提供相关示例或命令(如adb命令)。同时,提醒用户备份数据,避免进一步的数据丢失。最后,生成相关问题,帮助用户深入理解。</think>### 恢复或重新生成设备SN号的方法 设备SN号(序列号)是唯一标识硬件设备的关键信息,通常由厂商在生产时写入。若SN号丢失,恢复或重新生成需根据设备类型和丢失原因采取不同措施: #### 1. **通过物理标签或系统信息找回** - **物理标签检查**:大多数设备的SN号会印刷在机身、电池仓、包装盒或保修卡上。 - **系统设置查询**: - 手机/平板:进入`设置 > 关于手机 > 状态信息` - Windows电脑:命令行输入`wmic bios get serialnumber` - Linux设备:终端输入`sudo dmidecode -s system-serial-number` - **第三方工具**:如Android设备可通过ADB命令`adb shell getprop ro.serialno`获取[^2]。 #### 2. **通过厂商工具或服务恢复** - **刷机工具**:部分设备(如路由器、手机)可通过官方刷机工具重新写入SN号。例如: ```bash fastboot oem write-sn XXXXXX # Android设备示例命令 ``` - **联系售后**:提供购买凭证、设备IMEI/MEID等信息,厂商可能从数据库调取原始SN号。 #### 3. **软件层修复(限特定场景)** - **系统备份还原**:若SN号存储在系统分区且曾备份过,可通过Recovery模式恢复备份。 - **修改系统文件**:Android设备可尝试编辑`/persist`分区下的SN配置文件(需Root权限),但存在风险。 #### 4. **SN号重生成的限制** - **合法性**:私自生成SN号可能违反设备认证协议(如苹果T2芯片校验机制)。 - **技术门槛**:需逆向工程设备固件的SN生成算法,普通用户难以实现[^3]。 --- ### 注意事项 1. **备份优先**:操作前备份数据,避免因刷机/Root导致数据丢失。 2. **合法性验证**:修改SN号可能影响保修或触发反作弊机制(如游戏主机、智能设备)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值