udev是什么
-
動態管理設備文件
傳統的 Linux 系統通過靜態創建 /dev 目錄下的設備文件(如早期的 mknod 命令),但現代系統中硬件設備(如 USB 設備、存儲設備、串口等)熱插拔頻繁,udev 可實時響應設備事件,自動生成或刪除對應的設備文件。
例如:插入 U 盤時,udev 會自動在 /dev 下創建如 /dev/sdb 或 /dev/sdb1 等-設備節點。 -
設備屬性與權限配置
udev 可根據設備的硬件信息(如 Vendor ID、Product ID、總線類型等)為設備文件設置 自定義權限、所有者、組屬 或 符號鏈接(如 /dev/usb/dev_acm0 指向實際設備節點),方便用戶或程序訪問。
例如:為 USB 串口設備設置權限為 666,或為攝像頭設備創建易讀的符號鏈接 /dev/my_camera。 -
響應內核事件
udev 通過監聽內核的 uevent 機制(內核向用戶空間發送的設備事件通知),獲取設備的插拔、狀態變化等信息,并觸發預設的規則進行處理。 -
udev 規則文件
規則文件定義了設備匹配條件和對應的操作,存儲在 /etc/udev/rules.d/ 和 /lib/udev/rules.d/ 目錄下,文件名通常以數字開頭(如 50-udev-default.rules),數字越小優先級越高。
# 為 Vendor ID=046d、Product ID=c52f 的 USB 鼠標設置權限為 666,并創建符號鏈接
SUBSYSTEM=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52f", MODE="0666", SYMLINK+="usb_mouse"
SUBSYSTEM:設備所屬子系統(如 usb、block、tty 等)。
ATTRS{…}:設備屬性(如 Vendor ID、Product ID)。
MODE:設備文件權限。
SYMLINK:創建符號鏈接。
-
udevadm 工具
用于調試和管理 udev,常見命令:
udevadm monitor:監聽 udev 事件。
udevadm info:查看設備的 udev 規則和屬性。
udevadm trigger:手動觸發 udev 規則(如插入新設備后刷新設備節點)。 -
udev 與設備驅動的配合
在 Linux 設備驅動開發中,若需自動創建設備文件,通常配合以下步驟:
使用 class_create(內核函數)創建一個設備類(用于 /sys/class/ 下的分類)。
使用 device_create(內核函數)在該類下創建具體設備,此時內核會通過 uevent 通知 udev。
udev 根據預設規則或動態生成規則,在 /dev 下創建對應的設備文件。
驅動中自動創建設備文件示例
#include <linux/device.h>static struct class *my_class;
static struct device *my_device;// 驅動初始化函數中
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {return PTR_ERR(my_class);
}my_device = device_create(my_class, NULL, dev_num, NULL, "my_device");
if (IS_ERR(my_device)) {class_destroy(my_class);return PTR_ERR(my_device);
}// 驅動退出函數中
device_destroy(my_class, dev_num);
class_destroy(my_class);
執行后,udev 會根據 /sys/class/my_class/my_device 中的信息生成 /dev/my_device 設備文件。
class_create
struct class *class_create(struct module *owner, const char *name);
參數:
owner:一般為 THIS_MODULE(表示模塊所有者)。
name:設備類名稱(如 CLASS_NAME)。
返回值:
成功:指向 struct class 的指針。
失敗:ERR_PTR(需通過 IS_ERR() 檢查)。
device_create
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
參數:
class:指向 class_create 返回的 struct class 指針。
parent:父設備(通常為 NULL)。
devt:設備號(需與注冊字符設備時的 dev_num 一致)。
drvdata:設備特定數據(通常為 NULL)。
fmt:設備名稱(如 DEVICE_NAME)。
返回值:
成功:指向 struct device 的指針。
失敗:ERR_PTR(需通過 IS_ERR() 檢查)。
device_destroy:
void device_destroy(struct class *class, dev_t devt);
銷毀設備節點,刪除 /sys/class// 條目,并通知 udev 移除 /dev/ 下的設備文件。
class_destroy
void class_destroy(struct class *class);
銷毀設備類,刪除 /sys/class/ 目錄。
module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/kernel.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>#define DEVICE_NAME "mychartest" // 設備名(/dev/下的文件名)
#define CLASS_NAME "my_char_class" // 設備類名(/sys/class/下的目錄名)#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT#define rGPJ0CON *((volatile unsigned int *) GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *) GPJ0DAT)#define BUFFER_SIZE 100
#define MYMAJOR 250
#define MYNAME "mychartest"int mymajor;
static char buffer[BUFFER_SIZE];static dev_t dev_num; // 設備號(主設備號+次設備號)
static struct cdev my_cdev; // 字符設備結構static struct class *my_class; // 設備類指針
static struct device *my_device; // 設備指針int ctrled(int sta)
{if(sta){rGPJ0CON = 0x11111111;//rGPJ0DAT =((1<<3) | (1<<4) | (1<<5)) //滅1rGPJ0DAT =((1<<3) | (0<<4) | (0<<5)) ;//亮0printk(KERN_INFO "rGPJ0CON = %p\n",rGPJ0CON);printk(KERN_INFO "rGPJ0DAT = %d\n",rGPJ0DAT);printk(KERN_INFO "GPJ0CON = %p\n",GPJ0CON);printk(KERN_INFO "GPJ0DAT = %p\n",GPJ0DAT);}else{rGPJ0DAT =((0<<3) | (1<<4) | (1<<5)); //滅}}
// 打開設備函數
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 釋放設備函數
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 讀取設備函數
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_read\n");// 確保用戶緩沖區足夠大if (count < sizeof(unsigned int)) {return -EINVAL;}// 讀取寄存器值unsigned int reg_value = rGPJ0DAT;printk(KERN_INFO "reg_value = %d\n",reg_value);printk(KERN_INFO "sizeof(reg_value) = %d\n",sizeof(reg_value));if (copy_to_user(buf, ®_value, sizeof(reg_value))) {return -EFAULT;}// 更新文件位置*f_pos += sizeof(reg_value);// 返回實際傳輸的字節數return sizeof(reg_value);
}// 寫入設備函數
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_write\n");memset(buffer,0,sizeof(buffer));if (copy_from_user(buffer, buf, count)) {return -EFAULT;}if(!strcmp(buffer,"1")){printk(KERN_INFO "1\n");ctrled(1);}else if(!strcmp(buffer,"0")){printk(KERN_INFO "0\n");ctrled(0);}return 0;
}// 定義file_operations結構體實例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一樣,都這樣寫.open = my_open,.release = my_release,.write = my_write,.read = my_read,};// 模塊安裝函數
static int __init chrdev_init(void)
{ int ret;printk(KERN_INFO "chrdev_init helloworld init\n");// 動態分配設備號(主設備號自動分配,次設備號從0開始,分配1個)ret = alloc_chrdev_region(&dev_num, 0, 1, MYNAME);if (ret < 0) {printk(KERN_ERR "Failed to allocate char device number\n");return ret;}// 打印分配的主設備號和次設備號printk(KERN_INFO "Allocated major: %d, minor: %d\n", MAJOR(dev_num), MINOR(dev_num));// 初始化 cdev 結構cdev_init(&my_cdev, &my_fops);my_cdev.owner = THIS_MODULE;// 注冊字符設備到內核ret = cdev_add(&my_cdev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "Failed to add char device\n");unregister_chrdev_region(dev_num, 1); // 釋放已注冊的設備號return ret;}// 創建設備類(/sys/class/CLASS_NAME)my_class = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(my_class)) {cdev_del(&my_cdev);unregister_chrdev_region(dev_num, 1);printk(KERN_ERR "Failed to create class\n");return PTR_ERR(my_class);}// 創建設備節點(觸發 udev 生成 /dev/DEVICE_NAME)my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);if (IS_ERR(my_device)) {class_destroy(my_class);cdev_del(&my_cdev);unregister_chrdev_region(dev_num, 1);printk(KERN_ERR "Failed to create device\n");return PTR_ERR(my_device);}printk(KERN_INFO "Successfully created device: /dev/%s\n", DEVICE_NAME);printk(KERN_INFO "chrdev_init helloworld successs... mymajor = %d\n", MYMAJOR);
}// 模塊下載函數
static void __exit chrdev_exit(void)
{// 銷毀設備節點if (my_device) {device_destroy(my_class, dev_num);}// 銷毀設備類if (my_class) {class_destroy(my_class);}// 移除字符設備cdev_del(&my_cdev);// 釋放設備號unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chrdev_exit helloworld exit\n");}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx這種宏作用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston"); // 描述模塊的作者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
sys 文件系統簡介
sysfs(sys 文件系統) 是 Linux 內核提供的一種虛擬文件系統,用于在用戶空間中暴露內核數據結構和設備信息。它掛載在 /sys 目錄下,主要用于:
展示內核中設備、驅動、總線等硬件相關信息。
提供用戶空間與內核交互的接口(如讀取 / 修改內核參數)。
配合 udev 實現設備文件的自動創建和管理。
sysfs 的目錄結構與內核中的對象模型緊密關聯,主要包含以下幾類核心目錄:
/sys/bus:按總線類型(如 pci、usb)分類的設備和驅動信息。
/sys/devices:系統中所有設備的層次化列表。
/sys/class:按功能分類的設備抽象類(如 net、block、gpio),與用戶空間交互最密切。
/sys/module:內核加載的模塊信息。
/sys/class/xxx/ 目錄中文件的作用
/sys/class/ 下的每個子目錄(如 leds、tty、mydevice)代表一個 設備類(device class),用于將功能相似的設備分組。每個設備類包含多個設備實例的抽象信息,其下的文件和子目錄主要用于:
設備類屬性文件
name:設備類的名稱(字符串)。
dev:記錄該類下設備對應的主設備號和次設備號(格式為 major:minor),供 udev 自動創建設備節點時使用。
uevent:內核向用戶空間發送設備事件(如設備插入 / 移除)的接口,udev 通過監聽此文件觸發規則。
設備實例文件(在設備類子目錄中)
當設備驅動通過 class_create 和 device_create 創建設備實例后,會在設備類目錄下生成以設備名稱命名的子目錄(如 /sys/class/myclass/mydev),其下常見文件包括:
dev:設備實例對應的主設備號和次設備號(用于 udev 創建設備節點)。
power:設備電源管理相關屬性(如 status 表示電源狀態)。
subsystem:指向設備所屬的總線或類的符號鏈接(如 /sys/class/myclass)。
自定義屬性文件:驅動程序可通過 sysfs_create_file 在內核中動態創建屬性文件,用于暴露設備狀態或配置參數(如 LED 亮度、GPIO 方向等)。
與 udev 配合自動創建設備節點
/sys/class/xxx/ 目錄中的 dev 文件是 udev 自動創建設備節點的關鍵:
當內核檢測到新設備時,會在 sysfs 中生成對應的設備類和實例目錄,并寫入 dev 文件(主 / 次設備號)。
udev 監聽 sysfs 變化,根據預設規則(如 /etc/udev/rules.d/ 中的規則),結合 dev 文件中的設備號,在 /dev 目錄下創建設備節點(如 /dev/ttyUSB0)。
/sys/class/my_char_class/mychartest
dev 文件
作用:
該文件存儲設備的 設備號(包括主設備號和次設備號),用于標識內核中的具體設備。
設備號是字符設備或塊設備的唯一標識,由主設備號(標識設備類型 / 驅動)和次設備號(標識具體設備實例)組成。
用戶空間工具(如 mknod、udev)可通過讀取此文件獲取設備號,從而創建設備節點(如 /dev/xxx)。
內容格式:
文件內容為兩個用逗號分隔的數字,例如:
power/ 目錄
作用:
該目錄包含與設備 電源管理 相關的屬性文件,用于控制設備的電源狀態(如休眠、喚醒、功耗設置等)。
這是 Linux 內核電源管理子系統(PM subsystem)的一部分,支持 ACPI、USB 等設備的電源控制。
常見子文件:
control:
控制設備的電源狀態,可寫入 on(開啟)、auto(自動)、off(關閉)。
runtime_status:
顯示設備的運行時電源狀態(如 active、suspended)。
runtime_suspended_time:
記錄設備處于掛起狀態的總時間。
wakeup:
控制設備是否可以喚醒系統(on/off)。
echo off > /sys/class/xxx/power/control # 關閉設備電源
subsystem 文件
作用:
該文件存儲設備所屬的 子系統(Subsystem) 路徑,用于標識設備在 Linux 設備模型中的分類。
子系統是內核中設備的邏輯分組(如 block、char、net、usb 等),同一子系統下的設備具有相似的操作接口。
內容格式:
文件內容為子系統在 /sys/subsystem/ 下的相對路徑,例如:
char # 表示該設備屬于字符設備子系統
uevent 文件
作用:
該文件用于內核向用戶空間發送 設備熱插拔事件(uevent) 的相關信息。
當設備插入、移除或狀態改變時,內核會通過此文件傳遞事件參數(如設備類型、屬性變更等),供用戶空間工具(如 udev)處理。
內容格式:
文件內容為一系列 鍵值對,描述事件的具體信息,常見字段包括:
ACTION:事件類型(如 add、remove、change)。
DEVPATH:設備在 /sys 中的路徑。
SUBSYSTEM:設備所屬子系統。
DEVNAME:設備節點名稱(如 /dev/ttyUSB0)。
MAJOR/MINOR:設備號的主 / 次部分。