一、kobject_uevent簡介
在linux內核中,uevent機制是一種內核和用戶空間通信的機制,用于通知用戶空間應用程序各種硬件更改或其他事件,比如插入或移除硬件設備(如USB驅動器或網絡接口)。uevent表示“用戶空間事件”,當硬件事件發(fā)生時,內核會生成一個 uevent,并通過 netlink 套接字將其發(fā)送到用戶空間。用戶空間應用程序(例如 udev、mdev),可以監(jiān)聽這些事件并采取相應的操作,例如加載適當?shù)尿寗映绦蚧驁?zhí)行其他配置任務。
kobject_uevent()函數(shù)可通過發(fā)送一個uevent通知用戶空間:
intkobject_uevent(structkobject*kobj,enumkobject_actionaction) { returnkobject_uevent_env(kobj,action,NULL); }
從上述代碼可知,kobject_uevent()調用kobject_uevent_env()實現(xiàn)核心操作,但是kobject_uevent()沒有傳輸環(huán)境變量,故而設置kobject_uevent_env()的第三個參數(shù)為NULL。
enum kobject_action用于描述內核對象的操作,內核中定義了以下幾種操作:
enumkobject_action{ KOBJ_ADD,//對象添加。表示向系統(tǒng)中添加了一個新的kobject對象。 KOBJ_REMOVE,//對象移除。表示從系統(tǒng)中移除了一個kobject對象。 KOBJ_CHANGE,//對象修改。表示kobject對象的屬性或狀態(tài)發(fā)生了變化。 KOBJ_MOVE,//對象移動。表示kobject對象被移動到了另一個位置。 KOBJ_ONLINE,//對象上線。表示kobject對象已經準備好在線工作。 KOBJ_OFFLINE,//對象離線。表示kobject對象已經離線,不再處于工作狀態(tài)。 KOBJ_MAX//動作類型的最大值,用于邊界檢查。 };
在用戶空間可使用udevadm monitor查看uevent事件:
udevadmmonitor--udev
二、重要數(shù)據(jù)結構
1、struct kobj_uevent_env
struct kobj_uevent_env結構的目的是在內核中傳遞事件相關的參數(shù)、環(huán)境變量和數(shù)據(jù)。通過使用這個結構體,內核可以輕松地傳遞事件相關的信息和數(shù)據(jù)給相關的處理程序或模塊。
structkobj_uevent_env{ char*argv[3];//用于存儲傳遞給事件的參數(shù)。通常情況下,用于表示事件的命令行參數(shù)。 char*envp[UEVENT_NUM_ENVP];//這是一個包含UEVENT_NUM_ENVP個指針的數(shù)組,用于存儲傳遞給事件的環(huán)境變量。在Linux內核中,環(huán)境變量通常以鍵值對的形式傳遞。 intenvp_idx;//用于跟蹤環(huán)境變量數(shù)組中的當前索引。它指示下一個環(huán)境變量應該存儲在數(shù)組的哪個位置。 charbuf[UEVENT_BUFFER_SIZE];//用于存儲事件的文本數(shù)據(jù)。在內核中,事件通常以文本形式表示。 intbuflen;//用于跟蹤事件數(shù)據(jù)緩沖區(qū)中的當前有效數(shù)據(jù)長度。它指示緩沖區(qū)中包含的事件數(shù)據(jù)量。 };
2、struct kset_uevent_ops
struct kset_uevent_ops結構體定義了 kset 的事件操作接口,使得用戶可以通過提供相應的函數(shù)指針來自定義 kset 中 kobject 的事件處理行為:
structkset_uevent_ops{ int(*constfilter)(structkset*kset,structkobject*kobj); constchar*(*constname)(structkset*kset,structkobject*kobj); int(*constuevent)(structkset*kset,structkobject*kobj, structkobj_uevent_env*env); };
int (_ const filter)(struct kset _kset, struct kobject *kobj);:這是一個指向函數(shù)的指針,該函數(shù)用于過濾 kset 中的 kobject(內核對象)。它接受兩個參數(shù),分別是指向 kset 和 kobject 的指針,返回一個整數(shù)值,通常表示過濾操作的結果。
const char ( const name)(struct kset _kset, struct kobject _kobj);:這是一個指向函數(shù)的指針,該函數(shù)用于獲取 kobject 的名稱。它接受兩個參數(shù),分別是指向 kset 和 kobject 的指針,返回一個指向字符常量的指針,通常是 kobject 的名稱字符串。
int (_ const uevent)(struct kset _kset, struct kobject _kobj, struct kobj_uevent_env _env);:這是一個指向函數(shù)的指針,該函數(shù)用于生成 kobject 的事件。它接受三個參數(shù),分別是指向 kset、kobject 和 kobj_uevent_env 結構體的指針。該函數(shù)負責將 kobject 的事件信息填充到給定的環(huán)境中,并返回一個整數(shù)值,通常表示操作的成功或失敗。
三、kobject_uevent_env()詳細剖析
kobject_uevent_env()用于在給定的 kobject 上觸發(fā)一個事件,并且傳遞一個額外的環(huán)境變量數(shù)組:
intkobject_uevent_env(structkobject*kobj,enumkobject_actionaction,char*envp_ext[])
struct kobject *kobj:表示要觸發(fā)事件的內核對象。
enum kobject_action action:表示要執(zhí)行的動作,通常是一個枚舉類型,定義了可能的動作列表。這可能包括添加、刪除或修改對象等操作。
char *envp_ext[]:是一個字符指針數(shù)組,用于傳遞額外的環(huán)境變量給事件處理程序。這些環(huán)境變量以 key=value 的形式表示,其中 key 是環(huán)境變量的名稱,value 是其對應的值。
該函數(shù)將會觸發(fā)一個事件,并將指定的動作和環(huán)境變量信息傳遞給與該對象相關聯(lián)的事件處理程序。事件處理程序通常會根據(jù)傳入的動作和環(huán)境變量來執(zhí)行相應的操作,例如更新內核狀態(tài)、通知用戶空間進程等。kobject_uevent_env()實現(xiàn)如下(具體執(zhí)行步驟見注釋):
intkobject_uevent_env(structkobject*kobj,enumkobject_actionaction, char*envp_ext[]) { structkobj_uevent_env*env; constchar*action_string=kobject_actions[action]; constchar*devpath=NULL; constchar*subsystem; structkobject*top_kobj; structkset*kset; conststructkset_uevent_ops*uevent_ops; inti=0; intretval=0; /* *Mark"remove"eventdoneregardlessofresult,forsomesubsystems *donotwanttore-trigger"remove"eventviaautomaticcleanup. */ /如果動作是KOBJ_REMOVE,則將對象的state_remove_uevent_sent標志設置為1,表示“remove”事件已發(fā)送。 if(action==KOBJ_REMOVE) kobj->state_remove_uevent_sent=1; //打印調試信息,包括對象的名稱、指針和函數(shù)名稱。 pr_debug("kobject:'%s'(%p):%s ", kobject_name(kobj),kobj,__func__); //找該對象所屬的kset。 top_kobj=kobj; while(!top_kobj->kset&&top_kobj->parent) top_kobj=top_kobj->parent; //如果對象不屬于任何kset,則返回錯誤。 if(!top_kobj->kset){ pr_debug("kobject:'%s'(%p):%s:attemptedtosenduevent" "withoutkset! ",kobject_name(kobj),kobj, __func__); return-EINVAL; } kset=top_kobj->kset; uevent_ops=kset->uevent_ops; //檢查對象的uevent_suppress標志是否設置,如果設置了則跳過事件發(fā)送。 if(kobj->uevent_suppress){ pr_debug("kobject:'%s'(%p):%s:uevent_suppress" "causedtheeventtodrop! ", kobject_name(kobj),kobj,__func__); return0; } //如果uevent_ops和uevent_ops->filter存在且filter函數(shù)返回0,則跳過事件發(fā)送。 if(uevent_ops&&uevent_ops->filter) if(!uevent_ops->filter(kset,kobj)){ pr_debug("kobject:'%s'(%p):%s:filterfunction" "causedtheeventtodrop! ", kobject_name(kobj),kobj,__func__); return0; } //獲取事件的子系統(tǒng)名稱。 if(uevent_ops&&uevent_ops->name) subsystem=uevent_ops->name(kset,kobj); else subsystem=kobject_name(&kset->kobj); if(!subsystem){ pr_debug("kobject:'%s'(%p):%s:unsetsubsystemcausedthe" "eventtodrop! ",kobject_name(kobj),kobj, __func__); return0; } //分配并初始化一個kobj_uevent_env結構體 env=kzalloc(sizeof(structkobj_uevent_env),GFP_KERNEL); if(!env) return-ENOMEM; //獲取對象的路徑。 devpath=kobject_get_path(kobj,GFP_KERNEL); if(!devpath){ retval=-ENOENT; gotoexit; } //添加默認的環(huán)境變量,包括動作(ACTION)、設備路徑(DEVPATH)和子系統(tǒng)(SUBSYSTEM)。 retval=add_uevent_var(env,"ACTION=%s",action_string); if(retval) gotoexit; retval=add_uevent_var(env,"DEVPATH=%s",devpath); if(retval) gotoexit; retval=add_uevent_var(env,"SUBSYSTEM=%s",subsystem); if(retval) gotoexit; //如果有傳入的額外環(huán)境變量,則將它們添加到事件環(huán)境中 if(envp_ext){ for(i=0;envp_ext[i];i++){ retval=add_uevent_var(env,"%s",envp_ext[i]); if(retval) gotoexit; } } //調用uevent_ops->uevent函數(shù),如果存在,以允許kset特定的操作添加額外的環(huán)境變量。 if(uevent_ops&&uevent_ops->uevent){ retval=uevent_ops->uevent(kset,kobj,env); if(retval){ pr_debug("kobject:'%s'(%p):%s:uevent()returned" "%d ",kobject_name(kobj),kobj, __func__,retval); gotoexit; } } //根據(jù)動作執(zhí)行額外的操作。如果是KOBJ_ADD,則標記對象已發(fā)送state_add_uevent_sent為1。 switch(action){ caseKOBJ_ADD: /* *Mark"add"eventsowecanmakesurewedeliver"remove" *eventtouserspaceduringautomaticcleanup.If *theobjectdidsendan"add"event,"remove"will *automaticallygeneratedbythecore,ifnotalreadydone *bythecaller. */ kobj->state_add_uevent_sent=1; break; caseKOBJ_UNBIND: zap_modalias_env(env); break; default: break; } mutex_lock(&uevent_sock_mutex); /*wewillsendanevent,sorequestanewsequencenumber*/ //獲取一個新的序列號并將其添加到事件環(huán)境中 retval=add_uevent_var(env,"SEQNUM=%llu",++uevent_seqnum); if(retval){ mutex_unlock(&uevent_sock_mutex); gotoexit; } //通過網絡廣播發(fā)送事件。 retval=kobject_uevent_net_broadcast(kobj,env,action_string, devpath); mutex_unlock(&uevent_sock_mutex); //如果配置了CONFIG_UEVENT_HELPER,則調用uevent_helper來執(zhí)行額外的操作。 #ifdefCONFIG_UEVENT_HELPER /*calluevent_helper,usuallyonlyenabledduringearlyboot*/ if(uevent_helper[0]&&!kobj_usermode_filter(kobj)){ structsubprocess_info*info; retval=add_uevent_var(env,"HOME=/"); if(retval) gotoexit; retval=add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); if(retval) gotoexit; retval=init_uevent_argv(env,subsystem); if(retval) gotoexit; retval=-ENOMEM; info=call_usermodehelper_setup(env->argv[0],env->argv, env->envp,GFP_KERNEL, NULL,cleanup_uevent_env,env); if(info){ retval=call_usermodehelper_exec(info,UMH_NO_WAIT); env=NULL;/*freedbycleanup_uevent_env*/ } } #endif exit: kfree(devpath); kfree(env); returnretval; }
總而言之,kobject_uevent_env()調用add_uevent_var()構建默認的信息(包括:ACTION、DEVPATH、SUBSYSTEM和自定義數(shù)據(jù)envp_ext),然后調用kobject_uevent_net_broadcast()發(fā)送uevent。
1、add_uevent_var()
add_uevent_var()是一個用于在內核中構建uevent環(huán)境的輔助函數(shù),它允許向 uevent 環(huán)境中添加一個鍵值對,表示內核對象事件中的一個屬性或信息。該函數(shù)實現(xiàn)如下:
intadd_uevent_var(structkobj_uevent_env*env,constchar*format,...) { va_listargs; intlen; if(env->envp_idx>=ARRAY_SIZE(env->envp)){ WARN(1,KERN_ERR"add_uevent_var:toomanykeys "); return-ENOMEM; } va_start(args,format); len=vsnprintf(&env->buf[env->buflen], sizeof(env->buf)-env->buflen, format,args); va_end(args); if(len>=(sizeof(env->buf)-env->buflen)){ WARN(1,KERN_ERR"add_uevent_var:buffersizetoosmall "); return-ENOMEM; } env->envp[env->envp_idx++]=&env->buf[env->buflen]; env->buflen+=len+1; return0; }
add_uevent_var()函數(shù)的作用是根據(jù)指定的格式將鍵值對添加到 uevent 環(huán)境中,該函數(shù)使用vsnprintf()函數(shù)將格式化字符串和參數(shù)組合成一個字符串,并將該字符串添加到 kobj_uevent_env 結構體的緩沖區(qū)中,如果成功添加鍵值對,則返回0;如果緩沖區(qū)空間不足導致添加失敗,則返回-ENOSPC。
使用add_uevent_var()函數(shù)可以方便地構建uevent環(huán)境,向用戶空間發(fā)送有關內核對象事件的信息。通常情況下,該函數(shù)用于在kobject_uevent_env()函數(shù)中構建uevent環(huán)境,以向用戶空間發(fā)送內核對象事件。
2、kobject_uevent_net_broadcast()
`kobject_uevent_net_broadcast()用于在網絡命名空間上廣播內核對象事件:
intkobject_uevent_net_broadcast(structkobject*kobj, structkobj_uevent_env*env, constchar*action_string, constchar*devpath)
(1)首先,它檢查是否啟用了網絡支持 (CONFIG_NET),因為這個功能是基于網絡命名空間的。
(2)它獲取與給定 kobject 相關聯(lián)的命名空間類型操作 (ops),以確定是否存在網絡命名空間。
(3)如果 ops 存在,且命名空間類型是網絡命名空間 (KOBJ_NS_TYPE_NET),則獲取該命名空間。
(4)如果存在網絡命名空間,它調用 uevent_net_broadcast_tagged 函數(shù),將事件廣播到指定的網絡命名空間。
(5)如果沒有找到網絡命名空間,或者網絡命名空間不支持事件廣播,它調用 uevent_net_broadcast_untagged 函數(shù),將事件廣播到所有未標記的網絡命名空間。
(6)最后,它返回廣播函數(shù)的返回值,指示事件廣播的成功或失敗。總的來說,這個函數(shù)負責根據(jù)對象的網絡命名空間屬性,將內核對象事件廣播到適當?shù)木W絡命名空間中。uevent_net_broadcast_tagged()和uevent_net_broadcast_untagged()本質上都調用netlink_broadcast()實現(xiàn)核心功能。
3、call_usermodehelper_setup()
call_usermodehelper_setup 函數(shù)是 Linux 內核中的一個函數(shù),用于設置和準備調用用戶空間輔助程序(usermode helper)。這個函數(shù)并不直接執(zhí)行用戶空間輔助程序,而是為其設置參數(shù)和環(huán)境,并返回一個 subprocess_info 結構體,用于后續(xù)的執(zhí)行。
這個函數(shù)通常在內核中的一些子系統(tǒng)中使用,例如 kobject_uevent_net_broadcast 中的調用。函數(shù)原型如下:
structsubprocess_info*call_usermodehelper_setup(char*path,char**argv, char**envp,gfp_tgfp_mask, int(*init)(structsubprocess_info*info, structcred*new), void(*cleanup)(structsubprocess_info*info), void*data);
path:用戶空間輔助程序的路徑。
argv:參數(shù)數(shù)組,用于傳遞給用戶空間輔助程序的命令行參數(shù)。
envp:環(huán)境變量數(shù)組,用于傳遞給用戶空間輔助程序的環(huán)境變量。
gfp_mask:內存分配標志。
init:一個可選的初始化函數(shù),用于在用戶空間輔助程序執(zhí)行前進行一些初始化操作。
cleanup:一個可選的清理函數(shù),用于在用戶空間輔助程序執(zhí)行完畢后進行清理操作。
data:可選的附加數(shù)據(jù),可以在初始化和清理函數(shù)中使用。
這個函數(shù)的作用是為用戶空間輔助程序設置參數(shù)和環(huán)境,以及提供初始化和清理函數(shù)。返回的 subprocess_info 結構體包含了執(zhí)行用戶空間輔助程序所需的所有信息,包括路徑、參數(shù)、環(huán)境等。一旦設置完成,用戶空間輔助程序就可以通過call_usermodehelper_exec 函數(shù)執(zhí)行。
4、call_usermodehelper_exec()
call_usermodehelper_exec 函數(shù)是 Linux 內核中與執(zhí)行用戶空間輔助程序(usermode helper)相關的一個函數(shù)。它負責實際執(zhí)行用戶空間輔助程序,并監(jiān)控其執(zhí)行狀態(tài)。通常情況下,它會在 call_usermodehelper_setup 函數(shù)之后被調用。
下面是call_usermodehelper_exec 函數(shù)原型:
intcall_usermodehelper_exec(structsubprocess_info*sub_info,enumumh_waitwait);
sub_info:指向 subprocess_info 結構體的指針,該結構體包含了執(zhí)行用戶空間輔助程序所需的所有信息,包括路徑、參數(shù)、環(huán)境等。
wait:指定是否等待用戶空間輔助程序執(zhí)行完成的標志,可以是 UMH_WAIT_EXEC(等待執(zhí)行完成)或 UMH_NO_WAIT(不等待執(zhí)行完成)。
call_usermodehelper_exec 函數(shù)的作用是執(zhí)行用戶空間輔助程序,并等待其執(zhí)行完成(如果需要)。在執(zhí)行期間,它會監(jiān)控用戶空間輔助程序的執(zhí)行狀態(tài),并在適當?shù)臅r候返回執(zhí)行結果。
這個函數(shù)通常在 Linux 內核中的一些子系統(tǒng)中使用,例如 kobject_uevent_net_broadcast 中的調用。通過調用用戶空間輔助程序,內核可以執(zhí)行一些需要借助用戶空間程序完成的任務,例如配置、初始化等。
四、uevent_helper機制
在 Linux 內核中,uevent_helper 是一個用戶空間輔助程序,用于處理內核對象事件(uevent)。它通常在內核啟動期間使用,負責處理設備管理相關的事件,例如設備的插入、拔出、狀態(tài)變化等。下面是一些使用uevent_helper的典型實踐:
設備熱插拔管理: 當一個設備被插入或拔出時,內核會生成相應的 uevent,并調用 uevent_helper 來處理這些事件。uevent_helper 可以根據(jù)事件類型執(zhí)行一些特定的操作,例如加載適當?shù)脑O備驅動、更新設備管理信息等。
自動配置和初始化: 在系統(tǒng)啟動期間,內核可能需要執(zhí)行一些自動配置和初始化任務,例如掛載文件系統(tǒng)、加載網絡配置、啟動服務等。uevent_helper 可以被用于執(zhí)行這些任務,并根據(jù)事件類型執(zhí)行相應的初始化操作。
系統(tǒng)監(jiān)控和管理: uevent_helper 還可以被用于系統(tǒng)監(jiān)控和管理任務,例如記錄事件日志、生成警報、執(zhí)行故障排除操作等。
用戶空間通知: uevent_helper 還可以與用戶空間的其他程序進行通信,例如向用戶空間的監(jiān)控程序發(fā)送通知或觸發(fā)相應的操作。
具體使用 uevent_helper 的方式取決于系統(tǒng)的需求和設計。通常情況下,它會被配置為一個可執(zhí)行文件或腳本,并在系統(tǒng)啟動時由內核調用。在使用uevent_helper時,需要確保其具有足夠的權限來執(zhí)行所需的操作,并確保其安全性和穩(wěn)定性。
五、mdev
mdev是Linux系統(tǒng)中用于設備管理的工具,具有以下特征:
mdev 是一個更輕量級的設備管理器,通常用于嵌入式系統(tǒng)和一些輕量級 Linux 發(fā)行版中。
由 BusyBox 提供,并用于在啟動時自動創(chuàng)建設備節(jié)點。
mdev 不支持復雜的規(guī)則配置,而是基于簡單的設備名匹配規(guī)則來創(chuàng)建設備節(jié)點。
所以綜上所述,mdev 更適用于嵌入式系統(tǒng)或者對資源有限的系統(tǒng),因為它更加輕量級,但功能也更加有限。
參考busybox源碼,mdev實現(xiàn)如下:
intmdev_main(intargcUNUSED_PARAM,char**argv) { enum{ MDEV_OPT_SCAN=1<0, ??MDEV_OPT_SYSLOG?????=?1?<1, ??MDEV_OPT_DAEMON?????=?1?<2, ??MDEV_OPT_FOREGROUND?=?1?<3, ?}; ?int?opt; ?RESERVE_CONFIG_BUFFER(temp,?PATH_MAX?+?SCRATCH_SIZE); ?INIT_G(); ?/*?We?can?be?called?as?hotplug?helper?*/ ?/*?Kernel?cannot?provide?suitable?stdio?fds?for?us,?do?it?ourself?*/ ?bb_sanitize_stdio(); ?/*?Force?the?configuration?file?settings?exactly?*/ ?umask(0); ?xchdir("/dev"); ?opt?=?getopt32(argv,?"^" ??"sS"?IF_FEATURE_MDEV_DAEMON("df")?"v" ??"?" ??"vv", ??&G.verbose); #if?ENABLE_FEATURE_MDEV_CONF ?G.filename?=?"/etc/mdev.conf"; ?if?(opt?&?(MDEV_OPT_SCAN|MDEV_OPT_DAEMON))?{ ??/*?Same?as?xrealloc_vector(NULL,?4,?0):?*/ ??G.rule_vec?=?xzalloc((1?<4)?*?sizeof(*G.rule_vec)); ?} #endif ?if?(opt?&?MDEV_OPT_SYSLOG)?{ ??openlog(applet_name,?LOG_PID,?LOG_DAEMON); ??logmode?|=?LOGMODE_SYSLOG; ?} #if?ENABLE_FEATURE_MDEV_DAEMON ?if?(opt?&?MDEV_OPT_DAEMON)?{ ??/*?Daemon?mode?listening?on?uevent?netlink?socket.?Fork?away ???*?after?initial?scan?so?that?caller?can?be?sure?everything ???*?is?up-to-date?when?mdev?process?returns. ???*/ ??int?fd?=?daemon_init(temp); ??if?(!(opt?&?MDEV_OPT_FOREGROUND))?{ ???/*?there?is?no?point?in?logging?to?/dev/null?*/ ???logmode?&=?~LOGMODE_STDIO; ???bb_daemonize_or_rexec(0,?argv); ??} ??daemon_loop(temp,?fd); ?} #endif ?if?(opt?&?MDEV_OPT_SCAN)?{ ??/* ???*?Scan:?mdev?-s ???*/ ??//初始化掃描 ??initial_scan(temp); ?}?else?{ ??//處理action ??process_action(temp,?getpid()); ??dbg1("%s?exiting",?curtime()); ?} ?if?(ENABLE_FEATURE_CLEAN_UP) ??RELEASE_CONFIG_BUFFER(temp); ?return?EXIT_SUCCESS; }
上述代碼中,具體實現(xiàn)步驟如下:
使用位掩碼枚舉定義了幾個選項(MDEV_OPT_SCAN、MDEV_OPT_SYSLOG、MDEV_OPT_DAEMON、MDEV_OPT_FOREGROUND)。
初始化一些配置變量并設置環(huán)境(INIT_G()、bb_sanitize_stdio()、umask(0)、xchdir("/dev"))。
解析了命令行參數(shù),根據(jù)參數(shù)的不同執(zhí)行不同的操作,比如掃描設備、啟用系統(tǒng)日志、以守護進程模式運行等。
根據(jù)程序運行的不同情況,執(zhí)行相應的操作,比如進行初始掃描、處理動作等。
-
內核
+關注
關注
3文章
1362瀏覽量
40228 -
Linux
+關注
關注
87文章
11225瀏覽量
208919 -
函數(shù)
+關注
關注
3文章
4304瀏覽量
62427
原文標題:linux設備驅動模型(uevent)
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論