Linux裝置模型(9)_device resource management
1. 前言
#建議每位Linux驅動程式工程師都花點時間閱讀本文。
本文介紹的主題非常實用,能夠解答一些困惑,也能讓我們的程式碼變得簡單、簡潔。先來看一個例子:
1: /* drivers/media/platform/soc_camera/mx1_camera.c, line 695 */ 2: static int __init mx1_camera_probe(struct platform_device *pdev) 3: { 4: ... 5: 6: res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 7: irq = platform_get_irq(pdev, 0); 8: if (!res || (int)irq exit; 11: } 12: 13: clk = clk_get(&pdev->dev, "csi_clk"); 14: if (IS_ERR(clk)) { 15: err = PTR_ERR(clk); 16: goto exit; 17: } 18: 19: pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL); 20: if (!pcdev) { 21: dev_err(&pdev->dev, "Could not allocate pcdev\n"); 22: err = -ENOMEM; 23: goto exit_put_clk; 24: } 25: 26: ... 27: 28: /* 29: * Request the regions. 30: */ 31: if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) { 32: err = -EBUSY; 33: goto exit_kfree; 34: } 35: 36: base = ioremap(res->start, resource_size(res)); 37: if (!base) { 38: err = -ENOMEM; 39: goto exit_release; 40: } 41: ... 42: 43: /* request dma */ 44: pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH); 45: if (pcdev->dma_chan dev, "Can't request DMA for MX1 CSI\n"); 47: err = -EBUSY; 48: goto exit_iounmap; 49: } 50: ... 51: 52: /* request irq */ 53: err = claim_fiq(&fh); 54: if (err) { 55: dev_err(&pdev->dev, "Camera interrupt register failed\n"); 56: goto exit_free_dma; 57: } 58: 59: ... 60: err = soc_camera_host_register(&pcdev->soc_host); 61: if (err) 62: goto exit_free_irq; 63: 64: dev_info(&pdev->dev, "MX1 Camera driver loaded\n"); 65: 66: return 0; 67: 68: exit_free_irq: 69: disable_fiq(irq); 70: mxc_set_irq_fiq(irq, 0); 71: release_fiq(&fh); 72: exit_free_dma: 73: imx_dma_free(pcdev->dma_chan); 74: exit_iounmap: 75: iounmap(base); 76: exit_release: 77: release_mem_region(res->start, resource_size(res)); 78: exit_kfree: 79: kfree(pcdev); 80: exit_put_clk: 81: clk_put(clk); 82: exit: 83: return err; 84: }
相信每個編寫過Linux驅動程式的工程師都曾在probe函數中遇到類似的問題:需要按照特定順序申請多種資源(IRQ、Clock、記憶體、區域、ioremap、DMA等),如果其中一種資源申請失敗,就需要回滾並釋放之前所有已經申請的資源。因此,函數的結尾通常會有很多goto標籤(如exit_free_irq、exit_free_dma等),以便在資源申請失敗時小心翼翼地跳到正確的標籤處以釋放已申請的資源。
然而,這樣的程式碼充滿了大段的重複程式碼,包含著許多「if (condition) { err = xxx; goto xxx; }」的邏輯,不僅浪費時間,容易出錯,還不美觀。但是,我們有困惑,就會有改善的空間。最終,Linux裝置模型借助裝置資源管理(device resource management)為我們解決了這個問題:驅動程式只需要申請資源即可,不必擔心如何釋放資源,裝置模型會為我們處理。最終,我們可以透過以下方式簡化驅動程式程式碼:
1: static int __init mx1_camera_probe(struct platform_device *pdev) 2: { 3: ... 4: 5: res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 6: irq = platform_get_irq(pdev, 0); 7: if (!res || (int)irq return -ENODEV; 9: } 10: 11: clk = devm_clk_get(&pdev->dev, "csi_clk"); 12: if (IS_ERR(clk)) { 13: return PTR_ERR(clk); 14: } 15: 16: pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); 17: if (!pcdev) { 18: dev_err(&pdev->dev, "Could not allocate pcdev\n"); 19: return -ENOMEM; 20: } 21: 22: ... 23: 24: /* 25: * Request the regions. 26: */ 27: if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), DRIVER_NAME)) { 28: return -EBUSY; 29: } 30: 31: base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); 32: if (!base) { 33: return -ENOMEM; 34: } 35: ... 36: 37: /* request dma */ 38: pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH); 39: if (pcdev->dma_chan dev, "Can't request DMA for MX1 CSI\n"); 41: return -EBUSY; 42: } 43: ... 44: 45: /* request irq */ 46: err = claim_fiq(&fh); 47: if (err) { 48: dev_err(&pdev->dev, "Camera interrupt register failed\n"); 49: return err; 50: } 51: 52: ... 53: err = soc_camera_host_register(&pcdev->soc_host); 54: if (err) 55: return err; 56: 57: dev_info(&pdev->dev, "MX1 Camera driver loaded\n"); 58: 59: return 0; 60: }
怎麼做到呢?注意上面“devm_”開頭的接口,答案就在那裡。不要再使用那些常規的資源申請接口,用devm_xxx的接口代替。為了保持相容,這些新介面和舊介面的參數保持一致,只是名字前面加了“devm_”,並多加一個struct device指標。
2. devm_xxx
下面列舉一些常用的資源申請接口,它們由各個framework(如clock、regulator、gpio、等等)基於device resource management實現。使用時,直接忽略「devm_」的前綴,後面剩下的部分,driver工程師都很熟悉。只要記住一點,driver可以只申請,不釋放,設備模型會幫忙釋放。不過如果為了嚴謹,在driver remove時,可以主動釋放(也有相應的接口,這裡沒有列出)。
1: extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp); 2: 3: void __iomem *devm_ioremap_resource(struct device *dev, 4: struct resource *res); 5: void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, 6: unsigned long size); 7: 8: struct clk *devm_clk_get(struct device *dev, const char *id); 9: 10: int devm_gpio_request(struct device *dev, unsigned gpio, 11: const char *label); 12: 13: static inline struct pinctrl * devm_pinctrl_get_select( 14: struct device *dev, const char *name) 15: 16: static inline struct pwm_device *devm_pwm_get(struct device *dev, 17: const char *consumer); 18: 19: struct regulator *devm_regulator_get(struct device *dev, const char *id); 20: 21: static inline int devm_request_irq(struct device *dev, unsigned int irq, 22: irq_handler_t handler, unsigned long irqflags, 23: const char *devname, void *dev_id); 24: 25: struct reset_control *devm_reset_control_get(struct device *dev, 26: const char *id);
3. 什麼是「裝置資源」
一個裝置能運作,需要依賴很多的外部條件,如供電、時鐘等等,這些外部條件稱作設備資源(device resouce)。對於現代電腦的體系結構,可能的資源包括:
「
#a)power,供電。
b)clock,時鐘。
c)memory,內存,在kernel一般使用kzalloc分配。
d)GPIO,使用者和CPU交換簡單控制、狀態等資訊。
e)IRQ,觸發中斷。
f)DMA,無CPU參與情況下進行資料傳輸。
g)虛擬位址空間,一般使用ioremap、request_region等分配。
h)等等」
#而在Linux kernel的眼中,「資源」的定義更為廣義,例如PWM、RTC、Reset,都可以抽象化為資源,供driver使用。
在較早的kernel中,系統還不是特別複雜,且各個framework還沒有成型,因此大多的資源都由driver自行維護。但隨著系統複雜度的增加,driver之間共用資源的情況越來越多,同時電源管理的需求也越來越迫切。於是kernel就將各個resource的管理權收回,基於「device resource management」的框架,由各個framework統一管理,包括分配和回收。
4. device resource management的軟體框架
device resource management位於「drivers/base/devres.c」中,它的實作非常簡單,為什麼呢?因為資源的種類很多,表現形式也多種多樣,而devres不可能一一知情,也就不能進行具體的分配與回收。因此,devres能做的(也是它的唯一功能),就是:
“
提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。
”
而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。
其它的driver,位于这些framework之上,使用它们提供的机制和接口,开发起来就非常方便了。
5. 代码分析
5.1 数据结构
先从struct device开始吧!该结构中有一个名称为“devres_head”的链表头,用于保存该设备申请的所有资源,如下:
1: struct device { 2: ... 3: spinlock_t devres_lock; 4: struct list_head devres_head; 5: ... 6: } 7:
那资源的数据结构呢?在“drivers/base/devres.c”中,名称为struct devres,如下:
1: struct devres { 2: struct devres_node node; 3: /* -- 3 pointers */ 4: unsigned long long data[]; /* guarantee ull alignment */ 5: };
咋一看非常简单,一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。
node用于将devres组织起来,方便插入到device结构的devres_head链表中,因此一定也有一个list_head(见下面的entry)。另外,资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源,如下:
1: struct devres_node { 2: struct list_head entry; 3: dr_release_t release; 4: #ifdef CONFIG_DEBUG_DEVRES 5: const char *name; 6: size_t size; 7: #endif 8: };
抛开用于debug的变量不说,也很简单,一个entry list_head,一个release回调函数。看不出怎么抽象资源啊!别急,奥妙都在data这个零长度数组上面呢。
注1:不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的(是C文件哦!)。换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)!
5.2 一个无关话题:零长度数组
零长度数组的英文原名为Arrays of Length Zero,是GNU C的规范,主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。什么意思呢?
以struct devres为例,node变量的长度为3个指针的长度,而struct devres的长度也是3个指针的长度。而data只是一个标记,当有人分配了大于3个指针长度的空间并把它转换为struct devres类型的变量后,我们就可以通过data来访问多出来的memory。也就是说,有了零长度数组data,struct devres结构的长度可以不定,完全依赖于你分配的空间的大小。有什么用呢?
以本文的应用场景为例,多出来的、可通过data访问的空间,正是具体的device resource所占的空间。资源的类型不同,占用的空间的多少也不同,但devres模块的主要功能又是释放资源所占的资源。这是就是零长度数组的功能之一,因为整个memory空间是连续的,因此可以通过释devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。
零长度数组(data[0]),在不同的C版本中,有不同的实现方案,包括1长度数组(data[1])和不定长度数组(data[],本文所描述就是这一种),具体可参考GCC的规范:
https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
5.3 向上层framework提供的接口:devres_alloc/devres_free、devres_add/devres_remove
先看一个使用device resource management的例子(IRQ模块):
1: /* include/linux/interrupt.h */ 2: static inline int __must_check 3: devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, 4: unsigned long irqflags, const char *devname, void *dev_id) 5: { 6: return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, 7: devname, dev_id); 8: } 9: 10: 11: /* kernel/irq/devres.c */ 12: int devm_request_threaded_irq(struct device *dev, unsigned int irq, 13: irq_handler_t handler, irq_handler_t thread_fn, 14: unsigned long irqflags, const char *devname, 15: void *dev_id) 16: { 17: struct irq_devres *dr; 18: int rc; 19: 20: dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres), 21: GFP_KERNEL); 22: if (!dr) 23: return -ENOMEM; 24: 25: rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname, 26: dev_id); 27: if (rc) { 28: devres_free(dr); 29: return rc; 30: } 31: 32: dr->irq = irq; 33: dr->dev_id = dev_id; 34: devres_add(dev, dr); 35: 36: return 0; 37: } 38: EXPORT_SYMBOL(devm_request_threaded_irq); 39: 40: void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id) 41: { 42: struct irq_devres match_data = { irq, dev_id }; 43: 44: WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match, 45: &match_data)); 46: free_irq(irq, dev_id); 47: } 48: EXPORT_SYMBOL(devm_free_irq);
前面我们提过,上层的IRQ framework,会提供两个和request_irq/free_irq基本兼容的接口,这两个接口的实现非常简单,就是在原有的实现之上,封装一层devres的操作,如要包括:
1)一个自定义的数据结构(struct irq_devres),用于保存和resource有关的信息(对中断来说,就是IRQ num),如下:
1: /* 2: * Device resource management aware IRQ request/free implementation. 3: */ 4: struct irq_devres { 5: unsigned int irq; 6: void *dev_id; 7: };
2)一个用于release resource的回调函数(这里的release,和memory无关,例如free IRQ),如下:
1: static void devm_irq_release(struct device *dev, void *res) 2: { 3: struct irq_devres *this = res; 4: 5: free_irq(this->irq, this->dev_id); 6: }
“
因为回调函数是由devres模块调用的,由它的参数可知,struct irq_devres变量就是实际的“资源”,但对devres而言,它并不知道该资源的实际形态,因而是void类型指针。也只有这样,devres模块才可以统一的处理所有类型的资源。
”
3)以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。该接口的定义如下:
1: void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp) 2: { 3: struct devres *dr; 4: 5: dr = alloc_dr(release, size, gfp); 6: if (unlikely(!dr)) 7: return NULL; 8: return dr->data; 9: }
调用alloc_dr,分配一个struct devres类型的变量,并返回其中的data指针(5.2小节讲过了,data变量实际上是资源的代表)。alloc_dr的定义如下:
1: static __always_inline struct devres * alloc_dr(dr_release_t release, 2: size_t size, gfp_t gfp) 3: { 4: size_t tot_size = sizeof(struct devres) + size; 5: struct devres *dr; 6: 7: dr = kmalloc_track_caller(tot_size, gfp); 8: if (unlikely(!dr)) 9: return NULL; 10: 11: memset(dr, 0, tot_size); 12: INIT_LIST_HEAD(&dr->node.entry); 13: dr->node.release = release; 14: return dr; 15: }
看第一句就可以了,在资源size之前,加一个struct devres的size,就是total分配的空间。除去struct devres的,就是资源的(由data指针访问)。之后是初始化struct devres变量的node。
4)调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。
5)注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头(devres_head)中,该接口定义如下:
1: void devres_add(struct device *dev, void *res) 2: { 3: struct devres *dr = container_of(res, struct devres, data); 4: unsigned long flags; 5: 6: spin_lock_irqsave(&dev->devres_lock, flags); 7: add_dr(dev, &dr->node); 8: spin_unlock_irqrestore(&dev->devres_lock, flags); 9: }
从资源指针中,取出完整的struct devres指针,调用add_dr接口。add_dr也很简单,把struct devres指针挂到设备的devres_head中即可:
1: static void add_dr(struct device *dev, struct devres_node *node) 2: { 3: devres_log(dev, node, "ADD"); 4: BUG_ON(!list_empty(&node->entry)); 5: list_add_tail(&node->entry, &dev->devres_head); 6: }
6)如果失败,可以通过devres_free接口释放资源占用的空间,devm_free_irq接口中,会调用devres_destroy接口,将devres从devres_head中移除,并释放资源。这里就不详细描述了。
5.4 向设备模型提供的接口:devres_release_all
这里是重点,用于自动释放资源。
先回忆一下设备模型中probe的流程(可参考“Linux设备模型(5)_device和device driver”),devres_release_all接口被调用的时机有两个:
1)probe失败时,调用过程为(就不详细的贴代码了):
__driver_attach/__device_attach–>driver_probe_device—>really_probe,really_probe调用driver或者bus的probe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all。
2)deriver dettach时(就是driver remove时)
driver_detach/bus_remove_device–>__device_release_driver–>devres_release_all
devres_release_all的实现如下:
1: int devres_release_all(struct device *dev) 2: { 3: unsigned long flags; 4: 5: /* Looks like an uninitialized device structure */ 6: if (WARN_ON(dev->devres_head.next == NULL)) 7: return -ENODEV; 8: spin_lock_irqsave(&dev->devres_lock, flags); 9: return release_nodes(dev, dev->devres_head.next, &dev->devres_head, 10: flags); 11: }
以设备指针为参数,直接调用release_nodes:
1: static int release_nodes(struct device *dev, struct list_head *first, 2: struct list_head *end, unsigned long flags) 3: __releases(&dev->devres_lock) 4: { 5: LIST_HEAD(todo); 6: int cnt; 7: struct devres *dr, *tmp; 8: 9: cnt = remove_nodes(dev, first, end, &todo); 10: 11: spin_unlock_irqrestore(&dev->devres_lock, flags); 12: 13: /* Release. Note that both devres and devres_group are 14: * handled as devres in the following loop. This is safe. 15: */ 16: list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) { 17: devres_log(dev, &dr->node, "REL"); 18: dr->node.release(dev, dr->data); 19: kfree(dr); 20: } 21: 22: return cnt; 23: }
release_nodes会先调用remove_nodes,将设备所有的struct devres指针从设备的devres_head中移除。然后,调用所有资源的release回调函数(如5.3小节描述的devm_irq_release),回调函数会回收具体的资源(如free_irq)。最后,调用free,释放devres以及资源所占的空间。
以上是Linux裝置模型(9)_device resource management的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本指南探討了用於監視和故障排除磁盤I/O性能的基本Linux工具,這是對服務器速度和應用程序響應的關鍵度量。 磁盤I/O性能直接影響從和書面讀取數據的速度

對於新的Linux用戶,識別連接的設備至關重要,尤其是USB驅動器。 本指南提供了幾種命令行方法來確定USB設備的名稱,這對於格式化等任務必不可少。 雖然USB驅動器經常自動安裝(例如, /

Linux系統,特別是磁盤空間有限的系統,最常見的問題之一就是根分區(/)空間耗盡。 出現此問題時,您可能會遇到以下錯誤: No space left on device 別慌!這只是表示您的根目錄(/ 分區)已滿,這是一個常見問題,尤其是在磁盤空間有限的系統或全天候運行的服務器上。 發生這種情況時,您可能會遇到以下問題: 無法安裝或升級軟件包。 系統啟動失敗。 服務無法啟動。 無法寫入日誌或臨時文件。 本文將引導您完成識別問題、安全清理空間以及防止再次發生此問題的實用步驟。這些說明適用於初

本文探討了Linux用戶的一流記事本替代方案。 記事本雖然在Windows上很棒,但缺少Linux版本。 本指南提供了適合各種需求和偏好的各種選擇。 最高記錄的替代方案

幾天前,我遇到了32位CentOS 8分佈,並決定在較舊的32位系統上進行測試。 啟動後,我發現了一個網絡連接問題。連接將下降,每次重新啟動後都需要手動修復。這個公關

讓我們澄清什麼構成不良扇區或不良區塊:這是硬盤驅動器或閃存的一部分,它變得不可讀取或無法寫入,這通常是由於對磁盤表面的物理損害或閃爍的閃存閃存晶體管。 累積

CP命令(“複製”)是Linux和其他類似Unix的系統的基本工具,用於復製文件和目錄。 雖然對本地文件傳輸有效,但對於基於網絡的副本,SCP(安全副本)是首選的

在Linux系統中使用rm命令刪除文件或目錄時,如果遇到以下錯誤: rm: cannot remove 'file-or-directory': Device or resource busy 不用擔心,這是一個常見問題,這意味著您嘗試刪除的文件或目錄當前正被系統或正在運行的進程使用。 錯誤原因 “設備或資源繁忙”消息表示文件或目錄正在使用中。為了避免破壞系統或導致數據丟失,Linux阻止刪除正在使用的文件。 常見原因包括: 您的終端當前位於要刪除的目錄內。 程序或進程正在使用該文件或目錄。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

禪工作室 13.0.1
強大的PHP整合開發環境

Atom編輯器mac版下載
最受歡迎的的開源編輯器

Dreamweaver CS6
視覺化網頁開發工具

WebStorm Mac版
好用的JavaScript開發工具

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中