5.linux设备驱动模型
1.linux設備驅動模型簡介
1.1、什么是設備驅動模型
(1)類class、總線bus、設備device、驅動driver
(2)kobject和對象生命周期
(3)sysfs
(4)udev
1.2、為什么需要設備驅動模型
(1)早期內核(2.4之前)沒有統一的設備驅動模型,但照樣可以用
(2)2.6版本中正式引入設備驅動模型,目的是在設備越來越多,功耗要求等新特性要求的情況下讓驅動體系更易用、更優秀。
(3)設備驅動模型負責統一實現和維護一些特性,諸如:電源管理、熱插拔、對象生命周期、用戶空間和驅動空間的交互等基礎設施
(4)設備驅動模型目的是簡化驅動程序編寫,但是客觀上設備驅動模型本身設計和實現很復雜。
1.3、驅動開發的2個點
(1)驅動源碼本身編寫、調試。重點在于對硬件的了解。
(2)驅動什么時候被安裝、驅動中的函數什么時候被調用。跟硬件無關,完全和設備驅動模型有關。
2.設備驅動模型的底層架構
2.1 kobject ? @@@@@@
樹形結構中每一個目錄與一個kobject對象相對應,其包含了目錄的組織結構和名字等信息。在Linux系統中, kobject結構體是組成設備驅動模型的基本結構。
(1)kobject提供了最基本的設備對象管理能力,每一個在內核中注冊的kobject對象都對應于sysfs文件系統中的一個目錄,而不是文件。
(2)各種對象最基本單元,提供一些公用型服務如:對象引用計數、維護對象鏈表、對象上鎖、對用戶空間的表示
(3)設備驅動模型中的各種對象其內部都會包含一個kobject
(4)地位相當于面向對象體系架構中的總基類
2.2 kobj_type? kobject的屬性? @@@@@
使用該kobject設備的共同屬性
(1)很多書中簡稱為ktype,每一個kobject都需要綁定一個ktype來提供相應功能
(2)關鍵點1:sysfs_ops,提供該對象在sysfs中的操作方法(show和store)
(2)關鍵點2:attribute,提供在sysfs中以文件形式存在的屬性,其實就是應用接口
屬性數組:
struct attribute {const char *name; //屬性的名字struct module *owner;//指向用于該屬性的模塊,已經不常使用mode_t mode; //屬性讀寫權限 };name:屬性的名字,對應某個目錄下的文件的名字
操作屬性數組的方法:
struct sysfs_ops { ssize t (*show) (struct kobject *, struct attribute *, char *);/*讀屬性操作函數*/ ssize t (*store) (struct kobject *,struct attribute *,const char *buf, size_t count);/*寫屬性操作函數*/ }show()函數用于讀取一個屬性到用戶空間。函數的第1個參數是要讀取的kobect的指針,它對應要讀的目錄;第2個參數是要讀的屬性;第3個參數是存放讀到的屬性的緩存區。當函數調用成功后,會返回實際讀取的數據長度,這個長度不能超過PAGESIZE個字節大小。
store()函數將屬性寫入內核中。函數的第1個參數是與寫相關的kobject的指針,它對應要寫的目錄:第2個參數是要寫的屬性;第3個參數是要寫入的數據;第4個參數是要寫入的參數長度。這個長度不能超過PAGE-SIZE個字節大小。只有當擁有屬性有寫權限時,才能調用store0函數。
2.3 kset ?? @@@@@
kset是具有相同類型的kobject的集合
kset包含了一個kobject,其實它相當于一個鏈表節點,雖然Kobject包含了kset元素
(1)kset的主要作用是做頂層kobject的容器類
(2)kset的主要目的是將各個kobject(代表著各個對象)組織出目錄層次架構
(3)可以認為kset就是為了在sysfs中弄出目錄,從而讓設備驅動模型中的多個對象能夠有層次有邏輯性的組織在一起
熱插拔事件:
內核將在什么時候產生熱插拔事件呢?當驅動程序將kobject注冊到設備驅動模型時, 會產生這些事件。也就是當內核調用kobject_add()和kobject_del()麗數時,會產生熱插拔事件。熱插拔事件產生時,內核會根據kobject的kset指針找到所屬的kset結構體,執行kset結構體中uevent_ops包含的熱插拔函數。這些函數的定義如下:
struct kset_uevent_ops {int (* const filter)(struct kset *kset, struct kobject *kobj);//決定是否向內核發送事件const char *(* const name)(struct kset *kset, struct kobject *kobj);//得到子程序的名字int (* const uevent)(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env); };kset 和 kobject 的關系
https://www.cnblogs.com/deng-tao/p/6026373.html
3.設備驅動模型三大組件
總結:
不管是平臺總線還是IIC總線都都有這樣的調用路線:
當系統發現了新設備或者新驅動就會掉用相應總線的Match()進行匹配,當找到后就會掉用相對應的總線的Probe函數,最后Probe函數再調用驅動自己的Probe函數
雖然平臺總線和IIC總線的實現有些不同,但是大體使一樣的
如下:
//platform 總線 int platform_driver_register(struct platform_driver *drv) {if (drv->probe)drv->driver.probe = platform_drv_probe;return driver_register(&drv->driver); } static int platform_drv_probe(struct device *_dev) {struct platform_driver *drv = to_platform_driver(_dev->driver);struct platform_device *dev = to_platform_device(_dev);return drv->probe(dev); }// IIC總線 static int i2c_device_probe(struct device *dev) {status = driver->probe(client, i2c_match_id(driver->id_table, client)); }?
3.1、總線
(1)物理上的真實總線及其作用(英文bus)
(2)驅動框架中的總線式設計
(3)bus_type結構體,關鍵是match函數和uevent函數
總線私有數據
struct bus type private {struct kset subsys;/*代表該bus子系統,里面的kobj是該bus的主kobj,也就是最頂層*/ struct kset *drivers kset;/*掛接到該總線上的所有驅動集合*/ struct kset *devices kset;/*掛接到該總線上的所有設備集合*/ struct klist klist devices;/*所有設備的列表,與devices kset中的1ist相同*/ struct klist klist drivers;/*所有驅動程序的列表,與drivers_kset中的1ist相同*/ struct blocking notifier head bus notifier;/**/ unsigned int drivers autoprobe:1;/*設置是否在驅動注冊時, 自動探測(probe)設備*/ struct bus type *bus;/*回指包含自己的總線*/ }總線屬性
struct bus_attribute {struct attribute attr;//總線屬性ssize_t (*show)(struct bus_type *bus, char *buf); //屬性讀函數ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);//屬性寫函數 };3.2、設備? 注意:結構體在不同的內核版中的成員是不同的
在Linux設備驅動模型中,每一個設備都由一個device結構體來描述。device結構體包含了設備所具有的一些通用信息。對于驅動開發人員來說,當遇到新設備時,需要定義一個新的設備結構體,將device作為新結構體的成員。這樣就可以在新結構體中定義新設備的一些信息,而設備通用的信息就使用device結構體來表示。使用device結構體的另一個好處是,可以通過device輕松地將新設備加入設備驅動模型的管理中。
device中的大多數函數被內核使用,驅動開發人員不用關注
(1)struct device是硬件設備在內核驅動框架中的抽象
(2)device_register用于向內核驅動框架注冊一個設備
(3)通常device不會單獨使用,而是被包含在一個具體設備結構體中,如struct usb_device
設備屬性
struct device_attribute {struct attribute attr;ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);ssize_t (*store)(struct device *dev, struct device_attribute *attr,const char *buf, size_t count); };用來在devic目錄下創建一個屬性文件
int device_create_file(struct device *dev, //創建const struct device_attribute *attr) void device_remove_file(struct device *dev, //刪除const struct device_attribute *attr)3.3、驅動
(1)struct device_driver是驅動程序在內核驅動框架中的抽象
(2)關鍵元素1:name,驅動程序的名字,很重要,經常被用來作為驅動和設備的匹配依據
(3)關鍵元素2:probe,驅動程序的探測函數,用來檢測一個設備是否可以被該驅動所管理
3.4、類
(1)相關結構體:struct class 和 struct class_device
(2)udev的使用離不開class
(3)class的真正意義在于作為同屬于一個class的多個設備的容器。也就是說,class是一種人造概念,目的就是為了對各種設備進行分類管理。當然,class在分類的同時還對每個類貼上了一些“標簽”,這也是設備驅動模型為我們寫驅動提供的基礎設施。
3.5、總結
(1)模型思想很重要,其實就是面向對象的思想
(2)全是結構體套結構體,對基本功(語言功底和大腦復雜度)要求很高
?
platform平臺總線概述
下面的結構體都在Linux/device.h中
Linux發明了一種虛擬的總線,稱為platform總線,相應的設備稱為platform_device,而驅動成為
platform_driver。
系統為platform總線定義了一個bus_type的實例platform_bus_type,其定義位于drivers/base/platform.c下
platform總線的bus_type實例platform_bus_type
這里要重點關注其match()成員函數,正是此成員函數確定了platform_device和platform_driver之間是如何進行匹配
?
static int platform_match(struct device *dev, struct device_driver *dr
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}
有4種可能性,
一是基于設備樹風格的匹配;
二是基于ACPI風格的匹配;
三是匹配ID表(即platform_device設備名是否出現在platform_driver的ID表內);
第四種是匹配platform_device設備名和驅動的名字。
?
對于Linux 2.6ARM平臺而言,對platform_device的定義通常在BSP的板文件中實現。
在板文件中,將所有 platform_device? 設備歸納為一個數組,最終通過platform_add_devices()函數統一注冊。
Linux 3.x之后, ARM Linux不太喜歡人們以編碼的形式去填寫platform_device和注冊,而傾向于根據設備樹中的內容自動展開platform_device。
?
由以上分析可知,在設備驅動中引入platform的概念至少有如下好處。
1)使得設備被掛接在一個總線上,符合Linux 2.6以后內核的設備模型。其結果是使配套的sysfs節點、設備電源管理都成為可能。
2)隔離BSP和驅動。在BSP中定義platform設備和設備使用的資源、設備的具體配置信息,而在驅動中,只需要通過通用API去獲取資源和數據,做到了板相關代碼和驅動代碼的分離,使得驅動具有更好的可擴展性和跨平臺性。
3)讓一個驅動支持多個設備實例。譬如DM9000的驅動只有一份,但是我們可以在板級添加多份DM9000的platform_device,它們都可以與唯一的驅動匹配
?
4.platform平臺總線工作原理1
4.1、何為平臺總線
(1)相對于usb、pci、i2c等物理總線來說,platform總線是虛擬的、抽象出來的。
(2)回顧裸機中講的,CPU與外部通信的2種方式:地址總線式連接和專用接口式連接。平臺總線對應地址總線式連接設備,也就是SoC內部集成的各種內部外設。
(3)思考:為什么要有平臺總線?進一步思考:為什么要有總線的概念?
?
4.2、平臺總線下管理的2員大將
(1)platform工作體系都定義在drivers/base/platform.c中
(2)兩個結構體:platform_device和platform_driver
(3)兩個接口函數:platform_device_register和platform_driver_register
struct platform_device {
?? ?const char?? ?* name;?? ??? ??? ?// 平臺總線下設備的名字
?? ?int?? ??? ?id; ?? //設備名加ID名就得到了設備文件文件名
?? ?struct device?? ?dev;?? ??? ?// 所有設備通用的屬性部分
?? ?u32?? ??? ?num_resources;?? ??? ?// 設備使用到的resource的個數
?? ?struct resource?? ?* resource;?? ?// 設備使用到的資源數組的首地址
?? ?const struct platform_device_id?? ?*id_entry;?? ?// 設備ID表
?? ?/* arch specific additions */
?? ?struct pdev_archdata?? ?archdata;?? ??? ??? ?// 自留地,用來提供擴展性的
};
struct platform_driver {
?? ?int (*probe)(struct platform_device *);?? ??? ?// 驅動探測函數
?? ?int (*remove)(struct platform_device *);?? ?// 去掉一個設備
?? ?void (*shutdown)(struct platform_device *);?? ?// 關閉一個設備
?? ?int (*suspend)(struct platform_device *, pm_message_t state);
?? ?int (*resume)(struct platform_device *);
?? ?struct device_driver driver;?? ??? ??? ??? ?// 所有設備共用的一些屬性
?? ?const struct platform_device_id *id_table;?? ?// 設備ID表
};
platform_driver.driver.name要等于platform_device.name
因為設備要靠這個文件來識別驅動
在Linux 2.6以后的設備驅動模型中,需關心總線、設備和驅動這3個實體,總線將設備和驅動綁定。在系統每注冊一個設備的時候,會尋找與之匹配的驅動;相反的,在系統每注冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。
5.platform平臺總線工作原理2
5.1、平臺總線體系的工作流程
(1)第一步:系統啟動時在bus系統中注冊platform
(2)第二步:內核移植的人負責提供platform_device
(3)第三步:寫驅動的人負責提供platform_driver
(4)第四步:platform的match函數發現driver和device匹配后,調用driver的probe函數來完成驅動的初始化和安裝,然后設備就工作起來了
5.2、代碼分析:platform本身注冊
(1)每種總線(不光是platform,usb、i2c那些也是)都會帶一個match方法,match方法用來對總線下的device和driver進行匹配。理論上每種總線的匹配算法是不同的,但是實際上一般都是看name的。
(2)platform_match函數就是平臺總線的匹配方法。
match函數的工作方法是:
如果有id_table就說明驅動可能支持多個設備,所以這時候要去對比id_table中所有的name,只要找到一個相同的就匹配上了不再找了
如果找完id_table都還沒找到就說明沒匹配上;
如果沒有id_table或者沒匹配上,那就直接對比device和driver的name,如果匹配上就匹配上了,如果還沒匹配上那就匹配失敗。
6.platform平臺總線工作原理3
6.1、以leds-s3c24xx.c為例來分析platform設備和驅動的注冊過程
(1)platform_driver_register
(2)platform_device_register
6.2、platdata怎么玩
(1)platdata其實就是設備注冊時提供的設備有關的一些數據(譬如設備對應的gpio、使用到的中斷號、設備名稱····)
(2)這些數據在設備和驅動match之后,會由設備方轉給驅動方。驅動拿到這些數據后,通過這些數據得知設備的具體信息,然后來操作設備。
(3)這樣做的好處是:驅動源碼中不攜帶數據,只負責算法(對硬件的操作方法)。現代驅動設計理念就是算法和數據分離,這樣最大程度保持驅動的獨立性和適應性。
6.3、match函數的調用軌跡
6.4、probe函數的功能和意義
mach-x210.c? 注冊平臺設備
//led 1,2,3 static struct s5pv210_led_platdata s5pv210_led1_pdata = {.name = "led1",.gpio = S5PV210_GPJ0(3),.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger = "heartbeat", };static struct s5pv210_led_platdata s5pv210_led2_pdata = {.name = "led2",.gpio = S5PV210_GPJ0(4),.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger = "heartbeat", };static struct s5pv210_led_platdata s5pv210_led3_pdata = {.name = "led3",.gpio = S5PV210_GPJ0(5),.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger = "heartbeat", };static struct platform_device s5pv210_led1 = {.name = "s5pv210_led",.id = 0,.dev = {.platform_data = &s5pv210_led1_pdata,}, };static struct platform_device s5pv210_led2 = {.name = "s5pv210_led",.id = 1,.dev = {.platform_data = &s5pv210_led2_pdata,}, };static struct platform_device s5pv210_led3 = {.name = "s5pv210_led",.id = 2,.dev = {.platform_data = &s5pv210_led3_pdata,}, }; /*******************************/?
先定義結構體:
#ifndef __ASM_ARCH_LEDSGPIO_H #define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"#define S5PV210_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */ #define S5PV210_LEDF_TRISTATE (1<<1) /* tristate to turn off */struct s5pv210_led_platdata {unsigned int gpio;unsigned int flags;char *name;char *def_trigger; };#endif /* __ASM_ARCH_LEDSGPIO_H */ #include <linux/module.h> // module_init module_exit #include <linux/init.h> // __init __exit #include <linux/fs.h> #include <linux/ioport.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/leds.h> #include <linux/platform_device.h> #include <linux/slab.h>#include <asm/uaccess.h> //copy_from_user #include <asm/string.h> #include <asm/io.h>#include <mach/gpio-bank.h> #include <mach/regs-gpio.h> #include <mach/gpio.h> #include <mach/leds-gpio.h>#define GPJ0CON S5PV210_GPJ0CON #define GPJ0DAT S5PV210_GPJ0DAT#define GPJ0_LED1 S5PV210_GPJ0(3) #define GPJ0_LED2 S5PV210_GPJ0(4) #define GPJ0_LED3 S5PV210_GPJ0(5)#define X210_LED_ON 0 #define X210_LED_OFF 1/* our context */ struct s5pv210_gpio_led {struct led_classdev cdev;struct s5pv210_led_platdata *pdata; };//轉換 static inline struct s5pv210_gpio_led * pdev_to_gpio(struct platform_device *dev)//由platform_device得到s5pv210_gpio_led{return platform_get_drvdata(dev); }static inline struct s5pv210_gpio_led * to_gpio(struct led_classdev *led_cdev)//由led_classdev得到s5pv210_gpio_led {return container_of(led_cdev, struct s5pv210_gpio_led, cdev); }//change brightness 0~255 void led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) {struct s5pv210_gpio_led *led = to_gpio(led_cdev);printk(KERN_INFO "This is led_brightness_set1\n");// 用戶設置的值就是brightnessswitch(brightness){case LED_OFF: //LED滅gpio_set_value(led->pdata->gpio, X210_LED_OFF);break;case LED_FULL: //LED亮gpio_set_value(led->pdata->gpio, X210_LED_ON);break;default:printk(KERN_INFO "anew input number\n");} }//get led brightness enum led_brightness brightness_get(struct led_classdev *led_cdev) {//enum led_brightness brightness;printk(KERN_INFO "This is led_brightness1\n"); return 0; }//platform 注冊函數 static int s5pv210_led_probe(struct platform_device *dev) {struct s5pv210_gpio_led *led = NULL;struct s5pv210_led_platdata *pdata = dev->dev.platform_data;int ret = -1;led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);if (led == NULL) {dev_err(&dev->dev, "No memory for device\n");return -ENOMEM;}//gpio registerret = gpio_request(pdata->gpio, pdata->name);//注冊gpioif (ret == 0){printk(KERN_ERR "gpio_request led 0 failed\n");//printk(KERN_ERR "ret = [%d]\n", ret);return -EINVAL;}else{//printk(KERN_ERR "ret = [%d]\n", ret);gpio_direction_output(pdata->gpio, 1);}//把led掛接到dev中的一個特殊變量上,方便后面的remove釋放ledplatform_set_drvdata(dev, led);//register our new led deviceled->cdev.name = pdata->name;//得到設備名字led->cdev.brightness = 0;led->cdev.brightness_set = led_brightness_set;led->cdev.brightness_get = brightness_get;led->pdata = pdata;ret = led_classdev_register(&dev->dev, &led->cdev);//在設備文件下注冊設備類ledsif(ret < 0){printk(KERN_ERR "led_classdev_register failed\n");return -EINVAL;}printk(KERN_INFO "led register success\n");return 0; }// platform卸載函數 static int s5pv210_led_remove(struct platform_device *dev) {struct s5pv210_gpio_led *led = pdev_to_gpio(dev);struct s5pv210_led_platdata *pdata = dev->dev.platform_data;led_classdev_unregister(&led->cdev);//卸載設備類gpio_free(pdata->gpio);kfree(led);//釋放led結構體的空間printk(KERN_INFO "led unregister success\n");return 0; }//led platform 結構體 static struct platform_driver s5pv210_led_driver = {//platform驅動結構體.probe = s5pv210_led_probe,.remove = s5pv210_led_remove,.driver = {.name = "s5pv210_led",.owner = THIS_MODULE,}, };static int __init s5pv210_led_init(void) {return platform_driver_register(&s5pv210_led_driver);//注冊設備驅動 }static void __exit s5pv210_led_exit(void) {platform_driver_unregister(&s5pv210_led_driver);//注銷設備驅動}module_init(s5pv210_led_init); module_exit(s5pv210_led_exit);// MODULE_xxx這種宏作用是用來添加模塊描述信息 MODULE_LICENSE("GPL"); // 描述模塊的許可證 MODULE_AUTHOR("ljj"); // 描述模塊的作者 MODULE_DESCRIPTION("s5pv210 led"); // 描述模塊的介紹信息 MODULE_ALIAS("s5pv210_led"); // 描述模塊的別名信息運行結果:
[root@liu led1]# cd /sys/class/leds/
[root@liu leds]# ls -l
total 0
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:03 led1 -> ../../devices/platform/s5pv210_led.0/leds/led1
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:03 led2 -> ../../devices/platform/s5pv210_led.1/leds/led2
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:03 led3 -> ../../devices/platform/s5pv210_led.2/leds/led3
?
[root@liu s5pv210_led.0]# cd /sys/bus/platform/devices/
[root@liu devices]# ls -l
s5pv210_led.0
s5pv210_led.1
s5pv210_led.2
[root@liu devices]# cd /sys/bus/platform/devices/s5pv210_led.0
[root@liu s5pv210_led.0]# ls
driver ? ? leds ? ? ? modalias ? power ? ? ?subsystem ?uevent
[root@liu s5pv210_led.0]# cd leds/
[root@liu leds]# ls
led1
[root@liu leds]# cd led1/
[root@liu led1]# ls
brightness ? ? ?max_brightness ?subsystem
device ? ? ? ? ?power ? ? ? ? ? uevent
[root@liu led1]# cd /sys/bus/platform/drivers/
[root@liu drivers]# ls
5pv210_led ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? //有些驅動沒寫
[root@liu drivers]# cd s5pv210_led/
[root@liu s5pv210_led]# ls
bind ? ? ? ? ? s5pv210_led.0 ?s5pv210_led.2 ?unbind
module ? ? ? ? s5pv210_led.1 ?uevent
[root@liu s5pv210_led]# ls -l
total 0
--w------- ? ?1 root ? ? 0 ? ? ? ? ? ? 4096 Jan ?1 12:16 bind
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:16 module -> ../../../../module/module_test
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:16 s5pv210_led.0 -> ../../../../devices/platform/s5pv210_led.0
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:16 s5pv210_led.1 -> ../../../../devices/platform/s5pv210_led.1
lrwxrwxrwx ? ?1 root ? ? 0 ? ? ? ? ? ? ? ?0 Jan ?1 12:16 s5pv210_led.2 -> ../../../../devices/platform/s5pv210_led.2
--w------- ? ?1 root ? ? 0 ? ? ? ? ? ? 4096 Jan ?1 12:00 uevent
--w------- ? ?1 root ? ? 0 ? ? ? ? ? ? 4096 Jan ?1 12:16 unbind
?
?
?
總結
以上是生活随笔為你收集整理的5.linux设备驱动模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器人手眼标定都能搞不定?快戳,最好用的
- 下一篇: 广州科源980tc数控系统说明书_广州数