《Linux裝置節點建立》使用者空間ueventd建立裝置節點規則
一、devfs、udev和sysfs是什麼關係?
linux2.6之前使用devfs裝置檔案系統,它存在與核心空間;
linux2.6之後使用udev裝置檔案系統,它存在與使用者空間、但嚴重依賴與sysfs檔案系統。
二、Android(使用linux2.6以後的裝置節點建立策略)裝置節點的建立
在Android中,沒有獨立的類似於udev或者mdev的使用者程式,這個功能整合到了init中做了。程式碼見:system/core/init/init.c檔案,如下:
int ueventd_main(int argc, char **argv)
{
struct pollfd ufd;
int nr;
char tmp[32];
open_devnull_stdio();
log_init();
INFO("starting ueventd\n");
get_hardware_name(hardware, &revision);
/*
/ueventd.rc中以行為單位,除最後sysfs properties外,每一行由四部分組成:
如:/dev/diag 0660 radio radio
目錄 許可權 使用者ID(uid) 組ID(gid)
# sysfs properties 多一個屬性
/sys/devices/virtual/input/input* enable 0660 root input
目錄 屬性 許可權 使用者ID(uid) 組ID(gid)
*/
ueventd_parse_config_file("/ueventd.rc");
snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);
ueventd_parse_config_file
//初始化uevent,建立socket,執行coldboot,用於檢查當前service啟動前作業系統已經處理的事件,add這些事件到應用層
device_init();
ufd.events = POLLIN;
ufd.fd = get_device_fd();
//在死迴圈中處理觸發事件
while(1) {
ufd.revents = 0;
nr = poll(&ufd, 1, -1);
if (nr <= 0)
continue;
if (ufd.revents == POLLIN)
handle_device_fd();
}
}
int ueventd_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0); //讀取檔案內容返回給data;
if (!data) return -1;
parse_config(fn, data); //解析整個rc檔案內容
DUMP(); //空函式什麼都不做
return 0;
}
以上步驟和Init程序解析init.rc檔案的步驟相同,不過這裡呼叫的parse_config函式不同,該函式是專門用於解析ueventd.rc檔案的,具體解析過程如下:
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[UEVENTD_PARSER_MAXARGS]; //最多五個引數
int nargs;
nargs = 0;
state.filename = fn; //設定解析檔案的路徑
state.line = 1;
state.ptr = s; //檔案內容
state.nexttoken = 0;
state.parse_line = parse_line_device; //設定每行解析回撥函式
for (;;) {
int token = next_token(&state); //用於獲得配置檔案中特殊標記,如檔案結尾(T_EOF),換行符(T_TEXT),文字(T_NEWLINE) ,從檔案內容中查詢token,與init.rc檔案類似;
switch (token) {
case T_EOF: //檔案結束
state.parse_line(&state, 0, 0); //state.parse_line 呼叫函式為parse_line_device;
return;
case T_NEWLINE: //新的一行
if (nargs) {
state.parse_line(&state, nargs, args); //呼叫行解析函式解析每一行
nargs = 0;
}
break;
case T_TEXT:
if (nargs < UEVENTD_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}
函式首先查詢指定的token,然後對不同的token做不同的處理,對於發現新行時,呼叫parse_line_device函式對每一行進行詳細解析,該函式實現如下:
int next_token(struct parse_state *state)
{
char *x = state->ptr; //讀資料指標
char *s;
/*
#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2
非T_EOF時,直接返回下一個標記
*/
if (state->nexttoken) {
int t = state->nexttoken;
state->nexttoken = 0;
return t;
}
for (;;) {
switch (*x) {
case 0:
state->ptr = x;
return T_EOF;
case '\n':
x++;
state->ptr = x;
return T_NEWLINE; //換行符
case ' ':
case '\t':
case '\r':
x++;
continue; //跳過轉義字元 :空格 tab 回車
case '#':
while (*x && (*x != '\n')) x++; //單行註釋
if (*x == '\n') {
state->ptr = x+1;
return T_NEWLINE;
} else {
state->ptr = x;
return T_EOF;
}
default:
goto text;
}
}
textdone:
state->ptr = x;
*s = 0;
return T_TEXT;
text:
state->text = s = x;
textresume:
for (;;) {
switch (*x) {
case 0:
goto textdone;
case ' ':
case '\t':
case '\r':
x++;
goto textdone;
case '\n':
state->nexttoken = T_NEWLINE;
x++;
goto textdone;
case '"':
x++;
for (;;) {
switch (*x) {
case 0:
/* unterminated quoted thing */
state->ptr = x;
return T_EOF;
case '"':
x++;
goto textresume;
default:
*s++ = *x++;
}
}
break;
case '\\':
x++;
switch (*x) {
case 0:
goto textdone;
case 'n':
*s++ = '\n';
break;
case 'r':
*s++ = '\r';
break;
case 't':
*s++ = '\t';
break;
case '\\':
*s++ = '\\';
break;
case '\r':
/* \ <cr> <lf> -> line continuation */
if (x[1] != '\n') {
x++;
continue;
}
case '\n':
/* \ <lf> -> line continuation */
state->line++;
x++;
/* eat any extra whitespace */
while((*x == ' ') || (*x == '\t')) x++;
continue;
default:
/* unknown escape -- just copy */
*s++ = *x++;
}
continue;
default:
*s++ = *x++;
}
}
return T_EOF;
}
static void parse_line_device(struct parse_state* state, int nargs, char **args)
{
set_device_permission(nargs, args); //nargs引數個數 args引數
}
函式直接呼叫set_device_permission來實現;
非sysfs 裝置檔案:
|name| |permission| |user| |group|
/dev/cam 0660 root ca
sysfs 裝置檔案屬性:
/sys/devices/virtual/input/input* enable 0660 root input
void set_device_permission(int nargs, char **args)
{
char *name;
char *attr = 0;
mode_t perm;
uid_t uid;
gid_t gid;
int prefix = 0;
char *endptr;
int ret;
char *tmp = 0;
if (nargs == 0)
return;
if (args[0][0] == '#')
return;
/* |name| |permission| |user| |group| */
name = args[0];
if (!strncmp(name,"/sys/", 5) && (nargs == 5)) {
INFO("/sys/ rule %s %s\n",args[0],args[1]);
attr = args[1];
args++;
nargs--;
}
//引數檢查
if (nargs != 4) {
ERROR("invalid line ueventd.rc line for '%s'\n", args[0]);
return;
}
/* If path starts with [email protected] lookup the mount number. */
if (!strncmp(name, "[email protected]", 4)) {
int n = mtd_name_to_number(name + 4);
if (n >= 0)
asprintf(&tmp, "/dev/mtd/mtd%d", n);
name = tmp;
} else {
int len = strlen(name);
if (name[len - 1] == '*') {
prefix = 1;
name[len - 1] = '\0';
}
}
//許可權檢查
perm = strtol(args[1], &endptr, 8);
if (!endptr || *endptr != '\0') {
ERROR("invalid mode '%s'\n", args[1]);
free(tmp);
return;
}
//從android_ids陣列中查詢uid
ret = get_android_id(args[2]);
if (ret < 0) {
ERROR("invalid uid '%s'\n", args[2]);
free(tmp);
return;
}
uid = ret;
//從android_ids陣列中查詢gid
ret = get_android_id(args[3]);
if (ret < 0) {
ERROR("invalid gid '%s'\n", args[3]);
free(tmp);
return;
}
gid = ret;
//為裝置檔案新增許可權
add_dev_perms(name, attr, perm, uid, gid, prefix);
free(tmp);
}
首先檢查引數的合法性,並根據引數查詢uid、gid,對不同的使用者和組的uid、gid已經事先配置在陣列android_ids中了,如下:
- staticconststruct android_id_info android_ids[] = {
- { "root", AID_ROOT, },
- { "system", AID_SYSTEM, },
- { "radio", AID_RADIO, },
- { "bluetooth", AID_BLUETOOTH, },
- { "graphics", AID_GRAPHICS, },
- { "input", AID_INPUT, },
- { "audio", AID_AUDIO, },
- { "camera", AID_CAMERA, },
- { "log", AID_LOG, },
- { "compass", AID_COMPASS, },
- { "mount", AID_MOUNT, },
- { "wifi", AID_WIFI, },
- { "dhcp", AID_DHCP, },
- { "adb", AID_ADB, },
- { "install", AID_INSTALL, },
- { "media", AID_MEDIA, },
- { "drm", AID_DRM, },
- { "mdnsr", AID_MDNSR, },
- { "nfc", AID_NFC, },
- { "drmrpc", AID_DRMRPC, },
- { "shell", AID_SHELL, },
- { "cache", AID_CACHE, },
- { "diag", AID_DIAG, },
- { "net_bt_admin", AID_NET_BT_ADMIN, },
- { "net_bt", AID_NET_BT, },
- { "sdcard_r", AID_SDCARD_R, },
- { "sdcard_rw", AID_SDCARD_RW, },
- { "media_rw", AID_MEDIA_RW, },
- { "vpn", AID_VPN, },
- { "keystore", AID_KEYSTORE, },
- { "usb", AID_USB, },
- { "mtp", AID_MTP, },
- { "gps", AID_GPS, },
- { "inet", AID_INET, },
- { "net_raw", AID_NET_RAW, },
- { "net_admin", AID_NET_ADMIN, },
- { "net_bw_stats", AID_NET_BW_STATS, },
- { "net_bw_acct", AID_NET_BW_ACCT, },
- { "misc", AID_MISC, },
- { "nobody", AID_NOBODY, },
- };
這些uid、gid都是以巨集的形式被定義:
- #define AID_ROOT 0 /* traditional unix root user */
- #define AID_SYSTEM 1000 /* system server */
- #define AID_RADIO 1001 /* telephony subsystem, RIL */
- #define AID_BLUETOOTH 1002 /* bluetooth subsystem */
通過呼叫get_android_id函式在陣列android_ids中查詢對應的uid、gid
- staticint get_android_id(constchar *id)
- {
- unsigned int i;
- for (i = 0; i < ARRAY_SIZE(android_ids); i++)
- if (!strcmp(id, android_ids[i].name))
- return android_ids[i].aid;
- return 0;
- }
函式實現比較簡單,通過遍歷陣列,並匹配陣列元素的name屬性來查詢指定name的uid或gid。
最後通過add_dev_perms函式來設定裝置檔案的操作許可權,該函式定義在system\core\init\devices.c檔案中,在該檔案中聲明瞭三個連結串列:
- static list_declare(sys_perms);
- static list_declare(dev_perms);
- static list_declare(platform_names);
add_dev_perms函式就是將解析得到的裝置及裝置屬性,新增到指定的連結串列中,
使用解析得到的內容來建立一個perm_node變數,並根據條件新增到sys_perms或dev_perms連結串列中。
- int add_dev_perms(constchar *name, constchar *attr,
- mode_t perm, unsigned int uid, unsigned int gid,
- unsigned short prefix) {
- //建立perm_node
- struct perm_node *node = calloc(1, sizeof(*node));
- if (!node)
- return -ENOMEM;
- node->dp.name = strdup(name);
- if (!node->dp.name)
- return -ENOMEM;
- if (attr) {
- node->dp.attr = strdup(attr);
- if (!node->dp.attr)
- return -ENOMEM;
- }
- //設定perm_node的成員屬性
- node->dp.perm = perm;
- node->dp.uid = uid;
- node->dp.gid = gid;
- node->dp.prefix = prefix;
- //根據attr 來選擇新增到sys_perms或dev_perms連結串列中
- if (attr)
- list_add_tail(&sys_perms, &node->plist);
- else
- list_add_tail(&dev_perms, &node->plist);
- return 0;
- }
至此ueventd.rc檔案的解析工作完成了,uevent程序接下來將呼叫device_init()函式來初始化裝置檔案
- void device_init(void)
- {
- suseconds_t t0, t1;
- struct stat info;
- int fd;
- #ifdef HAVE_SELINUX
- struct selinux_opt seopts[] = {
- { SELABEL_OPT_PATH, "/file_contexts" }
- };
- if (is_selinux_enabled() > 0)
- sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
- #endif
- /* is 64K enough? udev uses 16MB! */
- //建立NETLINK socket,用於監聽核心傳送過來的uevent訊息
- device_fd = uevent_open_socket(64*1024, true);
- if(device_fd < 0)
- return;
- //設定socket相關屬性
- fcntl(device_fd, F_SETFD, FD_CLOEXEC);
- fcntl(device_fd, F_SETFL, O_NONBLOCK);
- //檢視"/dev/.coldboot_done" 檔案資訊
- if (stat(coldboot_done, &info) < 0) {
- t0 = get_usecs();
- coldboot("/sys/class");
- coldboot("/sys/block");
- coldboot("/sys/devices");
- t1 = get_usecs();
- fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);
- close(fd);
- log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));
- } else {
- log_event_print("skipping coldboot, already done\n");
- }
- }
函式首先呼叫uevent_open_socket 來建立PF_NETLINK socket 並繫結到指定地址上:
- int uevent_open_socket(int buf_sz, bool passcred)
- {
- struct sockaddr_nl addr;
- int on = passcred;
- int s;
- memset(&addr, 0, sizeof(addr));
- addr.nl_family = AF_NETLINK;
- addr.nl_pid = getpid();
- addr.nl_groups = 0xffffffff;
- //建立socket
- s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
- if(s < 0)
- return -1;
- //設定該socket屬性
- setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz));
- setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
- //繫結該socket
- if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
- close(s);
- return -1;
- }
- return s;
- }
ueventd程序接下來將通過系統呼叫poll函式來監控該socket,如下所示:
- ufd.events = POLLIN;
- ufd.fd = get_device_fd();
- while(1) {
- ufd.revents = 0;
- nr = poll(&ufd, 1, -1);
- if (nr <= 0)
- continue;
- if (ufd.revents == POLLIN)
- handle_device_fd();
- }
函式get_device_fd()返回建立的socket控制代碼值,並設定到ufd中,最後ueventd程序進入閉環監控模式,使用poll函式監控ufd,同時將第三個引數設定為-1,表示只有在監控的socket上有事件發生時,該函式才能返回。當熱插入某一裝置時,Linux核心將通過NETLINKsocket 傳送uevent事件,此時poll函式得以返回,並呼叫handle_device_fd()函式來出來裝置變化事件:
- void handle_device_fd()
- {
- char msg[UEVENT_MSG_LEN+2];
- int n;
- //從socket中讀取訊息內容
- while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {
- //如果讀取的內容長度大於1024,繼續讀取
- if(n >= UEVENT_MSG_LEN) /* overflow -- discard */
- continue;
- msg[n] = '\0';
- msg[n+1] = '\0';
- //將uevent訊息解析成uevent型別的事件
- struct uevent uevent;
- parse_event(msg, &uevent);
- //處理uevent事件
- handle_device_event(&uevent);
- handle_firmware_event(&uevent);
- }
- }
當有裝置事件發生時,poll函式返回,並從socket中讀取核心傳送過來的訊息內容,並將該訊息解析成uevent事件,同時呼叫handle_device_event函式和handle_firmware_event函式來分別處理裝置事件或firmware事件
- staticvoid handle_device_event(struct uevent *uevent)
- {
- //如果是裝置新增事件
- if (!strcmp(uevent->action,"add"))
- fixup_sys_perms(uevent->path);
- //塊裝置事件
- if (!strncmp(uevent->subsystem, "block", 5)) {
- handle_block_device_event(uevent);
- //平臺裝置事件
- } elseif (!strncmp(uevent->subsystem, "platform", 8)) {
- handle_platform_device_event(uevent);
- //通用裝置事件
- } else {
- handle_generic_device_event(uevent);
- }
- }
- staticvoid handle_firmware_event(struct uevent *uevent)
- {
- pid_t pid;
- int ret;
- if(strcmp(uevent->subsystem, "firmware"))
- return;
- if(strcmp(uevent->action, "add"))
- return;
- //建立一個執行緒來專門執行firmware事件
- /* we fork, to avoid making large memory allocations in init proper */
- pid = fork();
- if (!pid) {
- process_firmware_event(uevent);
- exit(EXIT_SUCCESS);
- }
- }
具體的處理過程這裡不在詳細分析,讀者有興趣請自行分析!至此就介紹完了整個ueventd程序的工作,
相關推薦
《Linux裝置節點建立》使用者空間ueventd建立裝置節點規則
一、devfs、udev和sysfs是什麼關係? linux2.6之前使用devfs裝置檔案系統,它存在與核心空間; linux2.6之後使用udev裝置檔案系統,它存在與使用者空間、但嚴重依賴與sysfs檔案系統。 二、Android(使用linux2.6以後的裝
Linux裝置節點建立》使用者空間ueventd建立裝置節點規則
說明:本文基於Android2.3和Linux2.6,其餘版本僅供參考。 一、devfs、udev和sysfs是什麼關係? linux2.6之前使用devfs裝置檔案系統,它存在與核心空間; linux2.6之後使用udev裝置檔案系統,它存在與使用者空間、但嚴重依賴與sy
android中usb裝置驅動不能自動建立裝置節點
除錯一個usb驅動,發現在android下無法自動建立裝置節點,手動建立裝置節點可以正常訪問硬體。後來發現是在init程序裡面對一些usb裝置進行了過濾。 在system/core/init/devices.c 中的下面函式中。 static void handle_gen
Linux 字元裝置驅動結構(二)—— 自動建立裝置節點
上一篇我們介紹到建立裝置檔案的方法,利用cat /proc/devices檢視申請到的裝置名,裝置號。 第一種是使用mknod手工建立:mknod filename type major minor 第二種是自動建立裝置節點:利用u
為什麼硬碟明明還有空間,linux卻說硬碟空間不足?inode;mkdir: 無法建立目錄"shen1": 裝置上沒有空間
現象:df -h顯示硬碟還有14G空間,但是touch file/mkdir directory都失敗,提示硬碟沒有空間 原因:df -ia檢視下inode的使用情況,發現已經爆了,(下圖顯示使用88%,還沒有用完) 那麼,inode究竟是什麼?為
linux驅動:自動建立裝置節點
在載入驅動模組後,就要自己使用mknod建立裝置節點,這樣雖然是可行的,但是比較麻煩。我們可以在__init()函式裡面新增一些函式,自動建立裝置節點。建立裝置節點使用了兩個函式 class_create()和device_create(),當然在__exit()函式裡,要使
linux driver ------ 字元裝置驅動之“ 建立裝置節點流程 ”
在字元裝置驅動開發的入門教程中,最常見的就是用device_create()函式來建立裝置節點了,但是在之後閱讀核心原始碼的過程中卻很少見device_create()的蹤影了,取而代之的是device_register()與device_add(),將device_create()函式展開不難發現:其實de
Linux裝置驅動第四天(自動建立裝置節點、LED驅動程式)
回顧: 與驅動有關的幾個重要結構體 1,struct cdev //從軟體上代表硬體裝置 { dev_t dev;//裝置號 = 主裝置號+次裝置號 struct file_operations f_ops; } 2,stru
linux用mknod建立裝置(節點)
mknod命令用於建立一個裝置檔案,即特殊檔案 首先要明白什麼是裝置檔案,簡單的我們說 作業系統與外部裝置(入磁碟驅動器,印表機,modern,終端 等等)都是通過裝置檔案來進行通訊的,在Unix/Linux系統與外部裝置通訊之前,這個裝置必須首先要有一個裝置檔案,裝置檔案
Linux /dev 自動建立裝置節點
ifeq ($(KERNELRELEASE),) #KERNEL_DIR:=/lib/modules/$(shell uname -r)/build/ KERNEL_DIR:=/usr/src/linux-headers-3.2.0-29-generic-pae PWD:=$(shell pwd) modul
《Linux裝置節點建立》手動與自動建立裝置節點
一、手動建立 1.驅動模組 test_driver.c //#include <linux/devfs_fs_kernel.h> #include <linux/module.h> #include <linux/types.h>
linux字元驅動之自動建立裝置節點
上一節中,我們是手工建立裝置節點,大家肯定也會覺得這樣做太麻煩了。 問:能不能讓系統自動建立裝置節點? 答:可以,linux有udev、mdev的機制,而我們的ARM開發板上移植的busybox有mdev機制,那麼就使用mdev機制來自動建立裝置節點。 問:檔案系統裡,在
一步一步學習 Linux 驅動之自動建立裝置節點
extern struct device *device_create(struct class *cls, struct device *parent, dev_t devt, vo
linux驅動開發之自動建立裝置節點
在有2.6系列版本中支援udev管理裝置檔案可以方便的建立裝置節點,不必使用mknod來建立,本文使用最小編碼來說明建立的幾個方法。 //主要用到的四個方法在linux/device.h定義: //建立類和釋放類的函式 建立成後將建立/sys/class/name資料
Linux驅動開發(5)——生成裝置節點
項裝置可以說是對一部分字元裝置的封裝,還有一部分不好歸類驅 動也歸到雜項裝置 雜項裝置初始化部分原始檔“drivers/char/ misc.c”,這一部分通過 Makefile可知,是強制編譯的。 雜項設備註冊標頭檔案include/linux/miscdevice
linux_DEVICE_ATTR建立裝置節點程式
一、簡述: 通過DEVICE_ATTR建立裝置節點,可以完成一些簡單的驅動的測試工作,可以向節點做echo cat相關的操作。 二、程式碼如下: (1)驅動程式碼: #include <linux/init.h> #include
linux_DEVICE_ATTR建立裝置節點程式[轉]
一、簡述: 通過DEVICE_ATTR建立裝置節點,可以完成一些簡單的驅動的測試工作,可以向節點做echo cat相關的操作。 二、程式碼如下: (1)驅動程式碼: #include <linux/init.h> #include <lin
Linux:核心之解析DTS裝置樹檔案並建立裝置的過程
核心之解析DTS裝置樹檔案並建立裝置的過程 在這裡,我分析的是核心原始碼來自谷歌官方Android7.1.2原始碼包經過打補丁包"SC60_Android7.1.2_Quectel_SDK_r270060_20180731.tar.gz"後得到的. 本文分析時使用的
Android音訊驅動-ASOC之建立裝置節點
建立裝置檔案的方法: 第一種是使用mknod手工建立:mknod filename type major minor 第二種是自動建立裝置節點:利用udev(mdev)來實現裝置檔案的自動建立,首先應保證支援udev(mdev),由busybox配置。
udev建立裝置節點的規則
1. 裝置要在下面任一一組目錄下 /sys/subsystem/devices /sys/bus/devices, /sys/class/, /sys/block 2. 上面這些目錄下面的裝置目錄裡要有uevent子項, 當用戶程式向uevent裡寫入add, kerne