linux内核的設備樹在哪裡修改?本文引用的内核代碼參考來自版本 linux-5.15.4 ,我來為大家科普一下關于linux内核的設備樹在哪裡修改?下面希望有你要的答案,我們一起來看看吧!
本文引用的内核代碼參考來自版本 linux-5.15.4 。
在 Linux 系統中,每個注冊到系統的設備都有一個編号,這個編号便是 Linux 系統中的設備号。
設備号作為一種系統資源,需要加以管理。否則,如果設備号與驅動程序對應關系錯誤,就會引起混亂或引起潛在的問題。
通過查看 /proc/devices 文件可以得到系統中注冊的設備,第一列為主設備号,第二列為設備名稱
$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
21 sg
...
Block devices:
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
...
一個設備号由主設備号和次設備号構成。
主設備号對應設備驅動程序,同一類設備一般使用相同的主設備号。
次設備号由驅動程序使用,驅動程序用來描述使用該驅動的設備的序号,序号一般從 0 開始。
Linux 設備号用 dev_t 類型的變量進行标識,這是一個 32位 無符号整數,内核源碼定義為:
/* <include/linux/types.h> */
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
主設備号用 dev_t 的高 12 位表示,次設備号用 dev_t 低 20 位表示。
内核提供了幾個宏定義,供驅動程序操作設備号時使用:
/* <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))
宏 MAJOR 從設備号 dev 中提取主設備号。宏 MINOR 用來從設備号 dev 中提取次設備号。宏 MKDEV 用來将主設備号 ma 和 次設備号 mi 組合成 dev_t 類型的設備号。
另外,内核也提供了從設備文件 i-節點結構(inode 結構體)中獲取主次設備号的函數,如下:
/* <include/linux/fs.h> */
/* 獲取次設備号 */
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
/* 獲取主設備号 */
static inline unsigned imajor(const struct inode *inode)
{
return MAJOR(inode->i_rdev);
}
通過函數源碼可知,獲取主設備号和次設備号最終是通過宏定義完成的。
内核管理設備号以字符設備為例,向内核中注冊設備号,内核是如何分配和管理設備号的呢?
在編寫字符設備驅動時,可以通過如下兩個系統調用向内核注冊設備号:
注冊一系列連續的字符設備号,主設備号需要函數調用者指定。此函數的原型為:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
參數 from 為設備編号,包含主設備号和次設備号。參數 count 用于指定連續設備号的個數,即當前驅動程序所管理的同類設備的個數。參數 name 為設備或驅動的名字。
執行成功,返回 0。失敗,則返回一個負值的錯誤碼。
注冊一系列連續的字符設備号,主設備号是由内核動态分配得到的。此函數的原型為:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
參數 dev 為函數的傳出參數,用于記錄動态分配的設備号,如果申請多個設備号,則此參數記錄這些連續設備号的起始值。
參數 baseminor 指定首個次設備号。參數 count 用于指定連續設備号的個數。參數 name 為設備或驅動的名字。
執行成功,返回 0。失敗,則返回一個負值的錯誤碼。
接下來,看看這兩個函數的内部實現流程。
register_chrdev_region()該函數的内核源碼為,關鍵部分已加注釋:
/* <fs.char_dev.c> */
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()實現的。
這個函數的主要功能是,将要使用的設備号注冊到内核的設備号管理體系中,避免多個驅動程序使用相同的設備号,而引起的混亂。
如果注冊設備号已經被使用,則會返回錯誤碼告知調用者,即調用失敗。如果成功,則函數返回 0。
struct char_device_struct
在調用過程中,會涉及到一個關鍵的數據結構 struct char_device_struct,其定義如下:
#define CHRDEV_MAJOR_HASH_SIZE 255
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];
定義結構體的同時,還定義了一個全局性的指針數組 chrdevs,是内核用來分配和管理設備号的。數組中的每一個元素都是指向 struct char_device_struct 類型的指針。
函數 register_chrdev_region() 的主要功能是将驅動程序要使用的設備号記錄到 chrdevs 數組中。
__register_chrdev_region()
核心處理函數 __register_chrdev_region() 内部,首先會分配一個 struct char_device_struct 類型的指針 cd,然後對其進行初始化(已經去除無關代碼):
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
...
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
/* 根據主設備号計算索引,搜索 chrdevs 數組,判斷主設備号是否可用 */
i = major_to_index(major);
for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next)
{
if (curr->major < major)
continue;
if (curr->major > major)
break;
if (curr->baseminor curr->minorct <= baseminor)
continue;
if (curr->baseminor >= baseminor minorct)
break;
goto out;
}
/* 初始化信息 */
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
/* 将分配的 cd 加入到 chrdevs[i] 中 */
if (!prev) {
cd->next = curr;
chrdevs[i] = cd;
} else {
cd->next = prev->next;
prev->next = cd;
}
...
}
函數申請完内存資源後,開始掃描 chrdevs 數組,确保當前注冊的設備号可用。如果設備号占用,函數返回錯誤碼,即調用失敗。
如果設備号可用,則用設備号和名字信息初始化。初始化完成後,将 struct char_device_struct 加入到内核管理設備号的鍊表中。
alloc_chrdev_region()此函數由内核動态分配設備号,該函數的内核源碼如下,關鍵部分已加注釋:
/* <fs.char_dev.c> */
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;
}
這個函數的核心處理也是由函數 __register_chrdev_region() 實現的。
與 register_chrdev_region() 相比,alloc_chrdev_region() 在調用 __register_chrdev_region() 時,第一個參數為 0。此時 __register_chrdev_region() 處理流程代碼如下,
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
...
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
/* 查找可用的主設備号 */
f (major == 0) {
ret = find_dynamic_major();
if (ret < 0) {
pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
name);
goto out;
}
major = ret;
}
...
}
在分配完成 struct char_device_struct 内存資源之後,通過 find_dynamic_major() 查找可用的主設備号。後續處理與 register_chrdev_region() 函數調用處理相同。
設備号分配成功後,将 struct char_device_struct 類型指針返回給 alloc_chrdev_region() 函數。然後再通過如下代碼将新分派的設備号返回給 alloc_chrdev_region() 調用者:
*dev = MKDEV(cd->major, cd->baseminor);
本文主要介紹了以下幾點内容:
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!