參考了 http://blog.sina.com.cn/s/blog_6100a4f101015uwh.html
原文:http://blog.csdn.net/pillarbuaa/article/details/9062115
Linux uevent機(jī)制
Uevent是內(nèi)核通知android有狀態(tài)變化的一種方法,比如USB線插入、拔出,電池電量變化等等。其本質(zhì)是內(nèi)核發(fā)送(可以通過(guò)socket)一個(gè)字符串,應(yīng)用層(android)接收并解釋該字符串,獲取相應(yīng)信息。
一、Kernel側(cè):
kernel 發(fā)送給battery_logging的uevent格式?
power_supply_uevent@kernel/drivers/power/power_supply_sysfs.c 該函數(shù)添加發(fā)送的信息到uevent
ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name); //psy->name="battery"
for (j = 0; j < psy->num_properties; j++) {
attr = &power_supply_attrs[psy->properties[j]]; //得到對(duì)應(yīng)的power supply attr,
attrname = kstruprdup(attr->attr.name, GFP_KERNEL);//會(huì)把屬性名字轉(zhuǎn)成大寫,比如“status”->"STATUS"
ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); //增加該屬性的信息到uevent buffer中
}
static enum power_supply_property msm_batt_power_props[] = { //對(duì)應(yīng)power_supply_attrs[]@kernel/drivers/power/power_supply_sysfs.c
POWER_SUPPLY_PROP_CHARGING_ENABLED, //表示這些屬性是被battery psy所需要的屬性,具體如何得到可查看
POWER_SUPPLY_PROP_STATUS, //qpnp_batt_power_get_property 函數(shù)
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
};
static struct device_attribute power_supply_attrs[] = {
/* Properties of type `int' */
POWER_SUPPLY_ATTR(status), //表示attr.name = "status"
POWER_SUPPLY_ATTR(charge_type),
...
}
UEVENT的發(fā)起在Kernel端,主要是通過(guò)函數(shù)
intkobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])
該函數(shù)的主要功能是根據(jù)參數(shù)組合一個(gè)字符串并發(fā)送。一個(gè)典型的字符串如下:
ACTION=change
DEVPATH=/devices/qpnp-bms-eab17000/power_supply/bms
SUBSYSTEM=power_supply
POWER_SUPPLY_NAME=bms
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_CAPACITY=42
POWER_SUPPLY_CURRENT_NOW=-426300
POWER_SUPPLY_CURRENT_MAX=6735829
POWER_SUPPLY_CHARGE_FULL_DESIGN=2350
SEQNUM=3488
ACTION=change
DEVPATH=/devices/qpnp-charger-eab16c00/power_supply/battery
SUBSYSTEM=power_supply
POWER_SUPPLY_NAME=battery
//和前面的msm_batt_power_props相對(duì)應(yīng)
POWER_SUPPLY_CHARGING_ENABLED=1
POWER_SUPPLY_STATUS=Charging
POWER_SUPPLY_CHARGE_TYPE=Fast
POWER_SUPPLY_HEALTH=Good
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_TECHNOLOGY=Li-ion
POWER_SUPPLY_VOLTAGE_MAX_DESIGN=4200000
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=4200000
POWER_SUPPLY_VOLTAGE_NOW=3833115
POWER_SUPPLY_CAPACITY=42
POWER_SUPPLY_CURRENT_NOW=-395600
POWER_SUPPLY_CHARGE_FULL_DESIGN=2350
POWER_SUPPLY_TEMP=250
POWER_SUPPLY_SYSTEM_TEMP_LEVEL=0
SEQNUM=3489
分析下UEvent如何傳遞給user space?
a. @kernel/drivers/power/qpnp-charger.c中只要有任何關(guān)于charger的變化,比如charger的各種irq handler
b. @kernel/drivers/power/qpnp-bms.c中calculate_soc_from_voltage和calculate_state_of_charge
c. @kernel/arch/arm/boot/dts/msm-pm8226.dtsi中有pm8226_bms的device定義,msm8226-cn3ii.dtsi中有pm8226_bms的定義補(bǔ)充
其中沒(méi)有 qcom,use-voltage-soc 的定義所以chip->use_voltage_soc=false, 也就是只用calculate_state_of_charge計(jì)算soc
順便chip->use_external_rsense=true,表示用外部的rsense.
d. @kernel/driver/power/power_supply_core.c中的power_supply_changed會(huì)被以上內(nèi)容調(diào)用
schedule_work(&psy->changed_work);
e. power_supply_changed_work@kernel/driver/power/power_supply_core.c
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); //action=KOBJ_CHANGE
f. kobject_uevent_env@kernel/lib/kobject_uevent.c
下面看這個(gè)函數(shù):
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];//獲取object的動(dòng)作
//藍(lán)色為為方便看代碼加入的
static const char *kobject_actions[] = {
[KOBJ_ADD] = "add",
[KOBJ_REMOVE] = "remove",
[KOBJ_CHANGE] = "change",
[KOBJ_MOVE] = "move",
[KOBJ_ONLINE] = "online",
[KOBJ_OFFLINE] = "offline",
};
//以上為kobject標(biāo)準(zhǔn)的動(dòng)作,調(diào)用時(shí)需要傳入相應(yīng)的enum值
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
const struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0;
#ifdef CONFIG_NET
struct uevent_sock *ue_sk;
#endif
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
=========================================================
這段代碼用來(lái)查找該kobject所屬于的kset,得到uevent_ops
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
"without kset!\n", kobject_name(kobj), kobj,
__func__);
return -EINVAL;
}
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
=========================================================
if (kobj->uevent_suppress) {
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
====================================================
獲取subsystem信息,
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: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}
=========================================================
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
devpath = kobject_get_path(kobj, GFP_KERNEL);//獲取kobject的設(shè)備路徑,
if (!devpath) {
retval = -ENOENT;
goto exit;
}
//下面準(zhǔn)備要傳遞的信息數(shù)據(jù)
retval = add_uevent_var(env, "ACTION=%s", action_string); //action_string="change"
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);//devpath="/devices/qpnp-charger-eab16c00/power_supply/battery"
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);//subsystem="power_supply"
if (retval)
goto exit;
//envp_ext[i]是傳進(jìn)來(lái)的參數(shù),為該event時(shí)攜帶的一些自定義的信息,
if (envp_ext) {//對(duì)于power_supply_changed_work->kobject_uevent->kobject_uevent_env(kobj, action, NULL);所以envp_ext=NULL
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);//會(huì)調(diào)用到power_supply_uevent@kernel/drivers/power/power_supply_sysfs.c,回到了前面的分析,
//也就是會(huì)添加具體的power battery信息到uevent buffer中了
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
goto exit;
}
}
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
//加入該event的序號(hào)
spin_lock(&sequence_lock);
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq); //用cat sys/kernel/uevent_seqnum 可以查看
if (retval)
goto exit;
#if defined(CONFIG_NET)
//下面通過(guò)網(wǎng)絡(luò)socket將數(shù)據(jù)發(fā)送出去
mutex_lock(&uevent_sock_mutex);
list_for_each_entry(ue_sk, &uevent_sock_list, list) {//在函數(shù)uevent_net_init會(huì)初始化一個(gè)uevent_sock,也只有一個(gè)
struct sock *uevent_sock = ue_sk->sk;
struct sk_buff *skb;
size_t len;
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);//申請(qǐng)網(wǎng)絡(luò)skb數(shù)據(jù)
if (skb) {
char *scratch;
//以下內(nèi)容是發(fā)送內(nèi)容打包
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
此時(shí)scratch中就增加了change@/devices/platform/msm-battery/power_supply/usb的
//組長(zhǎng)網(wǎng)絡(luò)skb數(shù)據(jù)結(jié)構(gòu)
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
NETLINK_CB(skb).dst_group = 1;//下面開(kāi)始發(fā)送數(shù)據(jù)
retval = netlink_broadcast_filtered(uevent_sock, skb, //該函數(shù)很關(guān)鍵后面再具體分析
0, 1, GFP_KERNEL,
kobj_bcast_filter,
kobj);
if (retval == -ENOBUFS || retval == -ESRCH)
retval = 0;
} else
retval = -ENOMEM;
}
mutex_unlock(&uevent_sock_mutex);
#endif
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
char *argv [3];
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
retval = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
基于Netlink的Uevent的具體實(shí)現(xiàn)
參考 http://www.ibm.com/developerworks/cn/linux/l-netlink/?ca=dwcn-newsletter-linux
http://www.chinaunix.net/old_jh/4/822500.html
1. Netlink 機(jī)制
在一臺(tái)運(yùn)行 Linux 的計(jì)算機(jī)中,CPU 在任何時(shí)候只會(huì)有如下四種狀態(tài):
【1】 在處理一個(gè)硬中斷。
【2】 在處理一個(gè)軟中斷,如 softirq、tasklet 和 bh。
【3】 運(yùn)行于內(nèi)核態(tài),但有進(jìn)程上下文,即與一個(gè)進(jìn)程相關(guān)。
【4】 運(yùn)行一個(gè)用戶態(tài)進(jìn)程。
其中,【1】、【2】和【3】是運(yùn)行于內(nèi)核空間的,而【4】是在用戶空間。其中除了【4】,其他狀態(tài)只可以被在其之上的狀態(tài)搶占。比如,軟中斷只可以被硬中斷搶占。
Linux 內(nèi)核模塊是一段可以動(dòng)態(tài)在內(nèi)核裝載和卸載的代碼,裝載進(jìn)內(nèi)核的代碼便立即在內(nèi)核中工作起來(lái)。Linux 內(nèi)核代碼的運(yùn)行環(huán)境有三種:用戶上下文環(huán)境、硬中斷環(huán)境和軟中斷環(huán)境。但三種環(huán)境的局限性分兩種,因?yàn)檐浿袛喹h(huán)境只是硬中斷環(huán)境的延續(xù)。比較如表【1】。
內(nèi)核態(tài)環(huán)境 | 介紹 | 局限性 |
用戶上下文 | 內(nèi)核態(tài)代碼的運(yùn)行與一用戶空間進(jìn)程相關(guān),如系統(tǒng)調(diào)用中代碼的運(yùn)行環(huán)境。 | 不可直接將本地變量傳遞給用戶態(tài)的內(nèi)存區(qū),因?yàn)閮?nèi)核態(tài)和用戶態(tài)的內(nèi)存映射機(jī)制不同。 |
硬中斷和軟中斷環(huán)境 | 硬中斷或軟中斷過(guò)程中代碼的運(yùn)行環(huán)境,如 IP 數(shù)據(jù)報(bào)的接收代碼的運(yùn)行環(huán)境,網(wǎng)絡(luò)設(shè)備的驅(qū)動(dòng)程序等。 | 不可直接向用戶態(tài)內(nèi)存區(qū)傳遞數(shù)據(jù); 代碼在運(yùn)行過(guò)程中不可阻塞。 |
Linux 傳統(tǒng)的進(jìn)程間通信有很多,如各類管道、消息隊(duì)列、內(nèi)存共享、信號(hào)量等等。但它們都無(wú)法介于內(nèi)核態(tài)與用戶態(tài)使用,原因如表【2】。
通信方法 | 無(wú)法介于內(nèi)核態(tài)與用戶態(tài)的原因 |
管道(不包括命名管道) | 局限于父子進(jìn)程間的通信。 |
消息隊(duì)列 | 在硬、軟中斷中無(wú)法無(wú)阻塞地接收數(shù)據(jù)。 |
信號(hào)量 | 無(wú)法介于內(nèi)核態(tài)和用戶態(tài)使用。 |
內(nèi)存共享 | 需要信號(hào)量輔助,而信號(hào)量又無(wú)法使用。 |
套接字 | 在硬、軟中斷中無(wú)法無(wú)阻塞地接收數(shù)據(jù)。 |
運(yùn)行在用戶上下文環(huán)境中的代碼是可以阻塞的,這樣,便可以使用消息隊(duì)列和 UNIX 域套接字來(lái)實(shí)現(xiàn)內(nèi)核態(tài)與用戶態(tài)的通信。但這些方法的數(shù)據(jù)傳輸效率較低,Linux 內(nèi)核提供 copy_from_user()/copy_to_user() 函數(shù)來(lái)實(shí)現(xiàn)內(nèi)核態(tài)與用戶態(tài)數(shù)據(jù)的拷貝,但這兩個(gè)函數(shù)會(huì)引發(fā)阻塞,所以不能用在硬、軟中斷中。一般將這兩個(gè)特殊拷貝函數(shù)用在類似于系統(tǒng)調(diào)用一類的函數(shù)中,此類函數(shù)在使用中往往"穿梭"于內(nèi)核態(tài)與用戶態(tài)。此類方法的工作原理路如圖【1】。
其中相關(guān)的系統(tǒng)調(diào)用是需要用戶自行編寫并載入內(nèi)核。 imp1.tar.gz是一個(gè)示例,內(nèi)核模塊注冊(cè)了一組設(shè)置套接字選項(xiàng)的函數(shù)使得用戶空間進(jìn)程可以調(diào)用此組函數(shù)對(duì)內(nèi)核態(tài)數(shù)據(jù)進(jìn)行讀寫。源碼包含三個(gè)文件,imp1.h 是通用頭文件,定義了用戶態(tài)和內(nèi)核態(tài)都要用到的宏。imp1_k.c 是內(nèi)核模塊的源代碼。imp1_u.c 是用戶態(tài)進(jìn)程的源代碼。整個(gè)示例演示了由一個(gè)用戶態(tài)進(jìn)程向用戶上下文環(huán)境發(fā)送一個(gè)字符串,內(nèi)容為"a message from userspace\n"。然后再由用戶上下文環(huán)境向用戶態(tài)進(jìn)程發(fā)送一個(gè)字符串,內(nèi)容為"a message from kernel\n"。
比起用戶上下文環(huán)境,硬中斷和軟中斷環(huán)境與用戶態(tài)進(jìn)程無(wú)絲毫關(guān)系,而且運(yùn)行過(guò)程不能阻塞。
1 使用一般進(jìn)程間通信的方法
我們無(wú)法直接使用傳統(tǒng)的進(jìn)程間通信的方法實(shí)現(xiàn)。但硬、軟中斷中也有一套同步機(jī)制--自旋鎖(spinlock),可以通過(guò)自旋鎖來(lái)實(shí)現(xiàn)中斷環(huán)境與中斷環(huán)境,中斷環(huán)境與內(nèi)核線程的同步,而內(nèi)核線程是運(yùn)行在有進(jìn)程上下文環(huán)境中的,這樣便可以在內(nèi)核線程中使用套接字或消息隊(duì)列來(lái)取得用戶空間的數(shù)據(jù),然后再將數(shù)據(jù)通過(guò)臨界區(qū)傳遞給中斷過(guò)程?;舅悸啡鐖D【2】。
因?yàn)橹袛噙^(guò)程不可能無(wú)休止地等待用戶態(tài)進(jìn)程發(fā)送數(shù)據(jù),所以要通過(guò)一個(gè)內(nèi)核線程來(lái)接收用戶空間的數(shù)據(jù),再通過(guò)臨界區(qū)傳給中斷過(guò)程。中斷過(guò)程向用戶空間的數(shù)據(jù)發(fā)送必須是無(wú)阻塞的。這樣的通信模型并不令人滿意,因?yàn)閮?nèi)核線程是和其他用戶態(tài)進(jìn)程競(jìng)爭(zhēng)CPU接收數(shù)據(jù)的,效率很低,這樣中斷過(guò)程便不能實(shí)時(shí)地接收來(lái)自用戶空間的數(shù)據(jù)。
在 Linux 2.4 版以后版本的內(nèi)核中,幾乎全部的中斷過(guò)程與用戶態(tài)進(jìn)程的通信都是使用 netlink 套接字實(shí)現(xiàn)的,同時(shí)還使用 netlink 實(shí)現(xiàn)了 ip queue 工具,但 ip queue 的使用有其局限性,不能自由地用于各種中斷過(guò)程。內(nèi)核的幫助文檔和其他一些 Linux 相關(guān)文章都沒(méi)有對(duì) netlink 套接字在中斷過(guò)程和用戶空間通信的應(yīng)用上作詳細(xì)的說(shuō)明,使得很多用戶對(duì)此只有一個(gè)模糊的概念。
netlink 套接字的通信依據(jù)是一個(gè)對(duì)應(yīng)于進(jìn)程的標(biāo)識(shí),一般定為該進(jìn)程的 ID。當(dāng)通信的一端處于中斷過(guò)程時(shí),該標(biāo)識(shí)為 0。當(dāng)使用 netlink 套接字進(jìn)行通信,通信的雙方都是用戶態(tài)進(jìn)程,則使用方法類似于消息隊(duì)列。但通信雙方有一端是中斷過(guò)程,使用方法則不同。netlink 套接字的最大特點(diǎn)是對(duì)中斷過(guò)程的支持,它在內(nèi)核空間接收用戶空間數(shù)據(jù)時(shí)不再需要用戶自行啟動(dòng)一個(gè)內(nèi)核線程,而是通過(guò)另一個(gè)軟中斷調(diào)用用戶事先指定的接收函數(shù)。工作原理如圖【3】。
很明顯,這里使用了軟中斷而不是內(nèi)核線程來(lái)接收數(shù)據(jù),這樣就可以保證數(shù)據(jù)接收的實(shí)時(shí)性。
當(dāng) netlink 套接字用于內(nèi)核空間與用戶空間的通信時(shí),在用戶空間的創(chuàng)建方法和一般套接字使用類似,但內(nèi)核空間的創(chuàng)建方法則不同。圖【4】是 netlink 套接字實(shí)現(xiàn)此類通信時(shí)創(chuàng)建的過(guò)程。
二android側(cè)
private finalUEventObserver mUEventObserver = newUEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
String state = event.get("USB_STATE");
String accessory = event.get("ACCESSORY");
//Added for USB Develpment debug, more log for more debuging help
if(DEBUG) Log.w(TAG, "mUEventObserver: onUEvent: state = " + state);
//Added for USB Develpment debug, more log for more debuging help
if (state != null) {
mHandler.updateState(state);
} else if ("START".equals(accessory)) {
if (DEBUG) Slog.d(TAG, "got accessory start");
setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false);
}
}
};
在類初始化時(shí)會(huì)調(diào)用下面的動(dòng)作,啟動(dòng)監(jiān)聽(tīng)動(dòng)作。
mUEventObserver.startObserving(USB_STATE_MATCH);
最終會(huì)調(diào)用到UEventObserver的addObserver:
private ArrayList<Object> mObservers = new ArrayList<Object>();
public void addObserver(String match, UEventObserver observer) {
synchronized(mObservers) {
mObservers.add(match);
mObservers.add(observer);
}
}
private static final String USB_STATE_MATCH =
"DEVPATH=/devices/virtual/android_usb/android0";
該函數(shù)最終會(huì)將”DEVPATH=/devices/virtual/android_usb/android0”增加到匹配序列中,當(dāng)kernel發(fā)送具有該字符串的數(shù)據(jù)時(shí),就返回匹配成功,然后調(diào)用mUEventObserver 的onUEvent函數(shù);
UeventObserver.java
private static class UEventThread extends Thread {
private ArrayList<Object> mObservers = new ArrayList<Object>();
UEventThread() {
super("UEventObserver");
}
public void run() {
native_setup();
byte[] buffer = new byte[1024];
int len;
while (true) {
len = next_event(buffer);
if (len > 0) {
String bufferStr = new String(buffer, 0, len); // easier to search a String
synchronized (mObservers) {
for (int i = 0; i < mObservers.size(); i += 2) {
if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {
((UEventObserver)mObservers.get(i+1))
.onUEvent(new UEvent(bufferStr));
}
}
}
}
}
}
聯(lián)系客服