《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(tmp);  
 
//初始化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 mtd@ lookup the mount number. */  
    if (!strncmp(name, "mtd@", 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中了,如下:

  1. static const struct android_id_info android_ids[] = {  
  2.     { "root",      AID_ROOT, },  
  3.     { "system",    AID_SYSTEM, },  
  4.     { "radio",     AID_RADIO, },  
  5.     { "bluetooth", AID_BLUETOOTH, },  
  6.     { "graphics",  AID_GRAPHICS, },  
  7.     { "input",     AID_INPUT, },  
  8.     { "audio",     AID_AUDIO, },  
  9.     { "camera",    AID_CAMERA, },  
  10.     { "log",       AID_LOG, },  
  11.     { "compass",   AID_COMPASS, },  
  12.     { "mount",     AID_MOUNT, },  
  13.     { "wifi",      AID_WIFI, },  
  14.     { "dhcp",      AID_DHCP, },  
  15.     { "adb",       AID_ADB, },  
  16.     { "install",   AID_INSTALL, },  
  17.     { "media",     AID_MEDIA, },  
  18.     { "drm",       AID_DRM, },  
  19.     { "mdnsr",     AID_MDNSR, },  
  20.     { "nfc",       AID_NFC, },  
  21.     { "drmrpc",    AID_DRMRPC, },  
  22.     { "shell",     AID_SHELL, },  
  23.     { "cache",     AID_CACHE, },  
  24.     { "diag",      AID_DIAG, },  
  25.     { "net_bt_admin", AID_NET_BT_ADMIN, },  
  26.     { "net_bt",    AID_NET_BT, },  
  27.     { "sdcard_r",  AID_SDCARD_R, },  
  28.     { "sdcard_rw", AID_SDCARD_RW, },  
  29.     { "media_rw",  AID_MEDIA_RW, },  
  30.     { "vpn",       AID_VPN, },  
  31.     { "keystore",  AID_KEYSTORE, },  
  32.     { "usb",       AID_USB, },  
  33.     { "mtp",       AID_MTP, },  
  34.     { "gps",       AID_GPS, },  
  35.     { "inet",      AID_INET, },  
  36.     { "net_raw",   AID_NET_RAW, },  
  37.     { "net_admin", AID_NET_ADMIN, },  
  38.     { "net_bw_stats", AID_NET_BW_STATS, },  
  39.     { "net_bw_acct", AID_NET_BW_ACCT, },  
  40.     { "misc",      AID_MISC, },  
  41.     { "nobody",    AID_NOBODY, },  
  42. };  

這些uid、gid都是以宏的形式被定義:

  1. #define AID_ROOT             0  /* traditional unix root user */  
  2. #define AID_SYSTEM        1000  /* system server */  
  3. #define AID_RADIO         1001  /* telephony subsystem, RIL */  
  4. #define AID_BLUETOOTH     1002  /* bluetooth subsystem */  

通過調用get_android_id函數在數組android_ids中查找對應的uid、gid

  1. static int get_android_id(const char *id)  
  2. {  
  3.     unsigned int i;  
  4.     for (i = 0; i < ARRAY_SIZE(android_ids); i++)  
  5.         if (!strcmp(id, android_ids[i].name))  
  6.             return android_ids[i].aid;  
  7.     return 0;  
  8. }  

函數實現比較簡單,通過遍歷數組,並匹配數組元素的name屬性來查找指定name的uid或gid。


最後通過add_dev_perms函數來設置設備文件的操作權限,該函數定義在system\core\init\devices.c文件中,在該文件中聲明瞭三個鏈表:

  1. static list_declare(sys_perms);  
  2. static list_declare(dev_perms);  
  3. static list_declare(platform_names);  

add_dev_perms函數就是將解析得到的設備及設備屬性,添加到指定的鏈表中,

使用解析得到的內容來創建一個perm_node變量,並根據條件添加到sys_perms或dev_perms鏈表中。



  1. int add_dev_perms(const char *name, const char *attr,  
  2.                   mode_t perm, unsigned int uid, unsigned int gid,  
  3.                   unsigned short prefix) {  
  4.     //創建perm_node  
  5.     struct perm_node *node = calloc(1, sizeof(*node));  
  6.     if (!node)  
  7.         return -ENOMEM;  
  8.   
  9.     node->dp.name = strdup(name);  
  10.     if (!node->dp.name)  
  11.         return -ENOMEM;  
  12.   
  13.     if (attr) {  
  14.         node->dp.attr = strdup(attr);  
  15.         if (!node->dp.attr)  
  16.             return -ENOMEM;  
  17.     }  
  18.     //設置perm_node的成員屬性  
  19.     node->dp.perm = perm;  
  20.     node->dp.uid = uid;  
  21.     node->dp.gid = gid;  
  22.     node->dp.prefix = prefix;  
  23.     //根據attr 來選擇添加到sys_perms或dev_perms鏈表中  
  24.     if (attr)  
  25.         list_add_tail(&sys_perms, &node->plist);  
  26.     else  
  27.         list_add_tail(&dev_perms, &node->plist);  
  28.     return 0;  


至此ueventd.rc文件的解析工作完成了,uevent進程接下來將調用device_init()函數來初始化設備文件

  1. void device_init(void)  
  2. {  
  3.     suseconds_t t0, t1;  
  4.     struct stat info;  
  5.     int fd;  
  6. #ifdef HAVE_SELINUX  
  7.     struct selinux_opt seopts[] = {  
  8.         { SELABEL_OPT_PATH, "/file_contexts" }  
  9.     };  
  10.     if (is_selinux_enabled() > 0)  
  11.         sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);  
  12. #endif  
  13.     /* is 64K enough? udev uses 16MB! */  
  14.     //創建NETLINK socket,用於監聽內核發送過來的uevent消息  
  15.     device_fd = uevent_open_socket(64*1024, true);  
  16.     if(device_fd < 0)  
  17.         return;  
  18.     //設置socket相關屬性  
  19.     fcntl(device_fd, F_SETFD, FD_CLOEXEC);  
  20.     fcntl(device_fd, F_SETFL, O_NONBLOCK);  
  21.     //查看"/dev/.coldboot_done" 文件信息  
  22.     if (stat(coldboot_done, &info) < 0) {  
  23.         t0 = get_usecs();  
  24.         coldboot("/sys/class");  
  25.         coldboot("/sys/block");  
  26.         coldboot("/sys/devices");  
  27.         t1 = get_usecs();  
  28.         fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000);  
  29.         close(fd);  
  30.         log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));  
  31.     } else {  
  32.         log_event_print("skipping coldboot, already done\n");  
  33.     }  
  34. }  

函數首先調用uevent_open_socket 來創建PF_NETLINK socket 並綁定到指定地址上:

  1. int uevent_open_socket(int buf_sz, bool passcred)  
  2. {  
  3.     struct sockaddr_nl addr;  
  4.     int on = passcred;  
  5.     int s;  
  6.       
  7.     memset(&addr, 0, sizeof(addr));  
  8.     addr.nl_family = AF_NETLINK;  
  9.     addr.nl_pid = getpid();  
  10.     addr.nl_groups = 0xffffffff;  
  11.     //創建socket  
  12.     s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);  
  13.     if(s < 0)  
  14.         return -1;  
  15.     //設置該socket屬性  
  16.     setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz));  
  17.     setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));  
  18.     //綁定該socket  
  19.     if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {  
  20.         close(s);  
  21.         return -1;  
  22.     }  
  23.     return s;  
  24. }  

ueventd進程接下來將通過系統調用poll函數來監控該socket,如下所示:

  1. ufd.events = POLLIN;  
  2. ufd.fd = get_device_fd();  
  3.   
  4. while(1) {  
  5.     ufd.revents = 0;  
  6.     nr = poll(&ufd, 1, -1);  
  7.     if (nr <= 0)  
  8.         continue;  
  9.     if (ufd.revents == POLLIN)  
  10.            handle_device_fd();  
  11. }  

函數get_device_fd()返回創建的socket句柄值,並設置到ufd中,最後ueventd進程進入閉環監控模式,使用poll函數監控ufd,同時將第三個參數設置爲-1,表示只有在監控的socket上有事件發生時,該函數才能返回。當熱插入某一設備時,Linux內核將通過NETLINKsocket 發送uevent事件,此時poll函數得以返回,並調用handle_device_fd()函數來出來設備變化事件:

  1. void handle_device_fd()  
  2. {  
  3.     char msg[UEVENT_MSG_LEN+2];  
  4.     int n;  
  5.     //從socket中讀取消息內容  
  6.     while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) {  
  7.         //如果讀取的內容長度大於1024,繼續讀取  
  8.         if(n >= UEVENT_MSG_LEN)   /* overflow -- discard */  
  9.             continue;  
  10.   
  11.         msg[n] = '\0';  
  12.         msg[n+1] = '\0';  
  13.         //將uevent消息解析成uevent類型的事件  
  14.         struct uevent uevent;  
  15.         parse_event(msg, &uevent);  
  16.         //處理uevent事件  
  17.         handle_device_event(&uevent);  
  18.         handle_firmware_event(&uevent);  
  19.     }  
  20. }  

當有設備事件發生時,poll函數返回,並從socket中讀取內核發送過來的消息內容,並將該消息解析成uevent事件,同時調用handle_device_event函數和handle_firmware_event函數來分別處理設備事件或firmware事件

  1. static void handle_device_event(struct uevent *uevent)  
  2. {  
  3.     //如果是設備添加事件  
  4.     if (!strcmp(uevent->action,"add"))  
  5.         fixup_sys_perms(uevent->path);  
  6.     //塊設備事件  
  7.     if (!strncmp(uevent->subsystem, "block", 5)) {  
  8.         handle_block_device_event(uevent);  
  9.     //平臺設備事件  
  10.     } else if (!strncmp(uevent->subsystem, "platform", 8)) {  
  11.         handle_platform_device_event(uevent);  
  12.     //通用設備事件  
  13.     } else {  
  14.         handle_generic_device_event(uevent);  
  15.     }  
  16. }  

 

  1. static void handle_firmware_event(struct uevent *uevent)  
  2. {  
  3.     pid_t pid;  
  4.     int ret;  
  5.     if(strcmp(uevent->subsystem, "firmware"))  
  6.         return;  
  7.     if(strcmp(uevent->action, "add"))  
  8.         return;  
  9.     //創建一個線程來專門執行firmware事件  
  10.     /* we fork, to avoid making large memory allocations in init proper */  
  11.     pid = fork();  
  12.     if (!pid) {  
  13.         process_firmware_event(uevent);  
  14.         exit(EXIT_SUCCESS);  
  15.     }  
  16. }  

具體的處理過程這裏不在詳細分析,讀者有興趣請自行分析!至此就介紹完了整個ueventd進程的工作,












發佈了369 篇原創文章 · 獲贊 587 · 訪問量 533萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章