知其然也要知其所以然---Kernel上报电量UEvent事件流程分析
1,
kernel-4.4\drivers\power\power_supply_core.c
当电量信息需要更新的时候,kernel会调用power_supply_changed_work这个工作队列,使用kobject_uevent函数往上发送uevent事件,action是KOBJ_CHANGE;
static void power_supply_changed_work(struct work_struct *work)
{unsigned long flags;struct power_supply *psy = container_of(work, struct power_supply,changed_work);dev_dbg(&psy->dev, "%s\n", __func__);spin_lock_irqsave(&psy->changed_lock, flags);/** Check 'changed' here to avoid issues due to race between* power_supply_changed() and this routine. In worst case* power_supply_changed() can be called again just before we take above* lock. During the first call of this routine we will mark 'changed' as* false and it will stay false for the next call as well.*/if (likely(psy->changed)) {printk("power_supply_changed_work 1111 \n" );psy->changed = false;spin_unlock_irqrestore(&psy->changed_lock, flags);class_for_each_device(power_supply_class, NULL, psy,__power_supply_changed_work);power_supply_update_leds(psy);atomic_notifier_call_chain(&power_supply_notifier,PSY_EVENT_PROP_CHANGED, psy);kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);spin_lock_irqsave(&psy->changed_lock, flags);}/** Hold the wakeup_source until all events are processed.* power_supply_changed() might have called again and have set 'changed'* to true.*/if (likely(!psy->changed))pm_relax(&psy->dev);spin_unlock_irqrestore(&psy->changed_lock, flags);
}
来看看kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);
/kernel-4.4/lib/kobject_uevent.c
372 int kobject_uevent(struct kobject *kobj, enum kobject_action action)
373 {
374 return kobject_uevent_env(kobj, action, NULL);
375 }
kobject_uevent_env代码相当长,由于已经配置了CONFIG_NET,在这里就只需要看netlink部分。
154 /**
155 * kobject_uevent_env - send an uevent with environmental data
156 *
157 * @action: action that is happening
158 * @kobj: struct kobject that the action is happening to
159 * @envp_ext: pointer to environmental data
160 *
161 * Returns 0 if kobject_uevent_env() is completed with success or the
162 * corresponding error when it fails.
163 */
164 int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
165 char *envp_ext[])
166 {
167 struct kobj_uevent_env *env;
168 const char *action_string = kobject_actions[action];
169 const char *devpath = NULL;
170 const char *subsystem;
171 struct kobject *top_kobj;
172 struct kset *kset;
173 const struct kset_uevent_ops *uevent_ops;
174 int i = 0;
175 int retval = 0;
176 #ifdef CONFIG_NET
177 struct uevent_sock *ue_sk;
178 #endif
179
180 pr_debug("kobject: '%s' (%p): %s\n",
181 kobject_name(kobj), kobj, __func__);
182
183 /* search the kset we belong to */
184 top_kobj = kobj;
185 while (!top_kobj->kset && top_kobj->parent)
186 top_kobj = top_kobj->parent;
187
188 if (!top_kobj->kset) {
189 pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
190 "without kset!\n", kobject_name(kobj), kobj,
191 __func__);
192 return -EINVAL;
193 }
194
195 kset = top_kobj->kset;
196 uevent_ops = kset->uevent_ops;
197
198 /* skip the event, if uevent_suppress is set*/
199 if (kobj->uevent_suppress) {
200 pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
201 "caused the event to drop!\n",
202 kobject_name(kobj), kobj, __func__);
203 return 0;
204 }
205 /* skip the event, if the filter returns zero. */
206 if (uevent_ops && uevent_ops->filter)
207 if (!uevent_ops->filter(kset, kobj)) {
208 pr_debug("kobject: '%s' (%p): %s: filter function "
209 "caused the event to drop!\n",
210 kobject_name(kobj), kobj, __func__);
211 return 0;
212 }
213
214 /* originating subsystem */
215 if (uevent_ops && uevent_ops->name)
216 subsystem = uevent_ops->name(kset, kobj);
217 else
218 subsystem = kobject_name(&kset->kobj);
219 if (!subsystem) {
220 pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
221 "event to drop!\n", kobject_name(kobj), kobj,
222 __func__);
223 return 0;
224 }
225
226 /* environment buffer */
227 env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
228 if (!env)
229 return -ENOMEM;
230
231 /* complete object path */
232 devpath = kobject_get_path(kobj, GFP_KERNEL);
233 if (!devpath) {
234 retval = -ENOENT;
235 goto exit;
236 }
237
238 /* default keys */
239 retval = add_uevent_var(env, "ACTION=%s", action_string);
240 if (retval)
241 goto exit;
242 retval = add_uevent_var(env, "DEVPATH=%s", devpath);
243 if (retval)
244 goto exit;
245 retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
246 if (retval)
247 goto exit;
248
249 /* keys passed in from the caller */
250 if (envp_ext) {
251 for (i = 0; envp_ext[i]; i++) {
252 retval = add_uevent_var(env, "%s", envp_ext[i]);
253 if (retval)
254 goto exit;
255 }
256 }
257
258 /* let the kset specific function add its stuff */
259 if (uevent_ops && uevent_ops->uevent) {
260 retval = uevent_ops->uevent(kset, kobj, env);
261 if (retval) {
262 pr_debug("kobject: '%s' (%p): %s: uevent() returned "
263 "%d\n", kobject_name(kobj), kobj,
264 __func__, retval);
265 goto exit;
266 }
267 }
268
269 /*
270 * Mark "add" and "remove" events in the object to ensure proper
271 * events to userspace during automatic cleanup. If the object did
272 * send an "add" event, "remove" will automatically generated by
273 * the core, if not already done by the caller.
274 */
275 if (action == KOBJ_ADD)
276 kobj->state_add_uevent_sent = 1;
277 else if (action == KOBJ_REMOVE)
278 kobj->state_remove_uevent_sent = 1;
279
280 mutex_lock(&uevent_sock_mutex);
281 /* we will send an event, so request a new sequence number */
282 retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
283 if (retval) {
284 mutex_unlock(&uevent_sock_mutex);
285 goto exit;
286 }
287
288 #if defined(CONFIG_NET)
289 /* send netlink message */
290 list_for_each_entry(ue_sk, &uevent_sock_list, list) {
291 struct sock *uevent_sock = ue_sk->sk;
292 struct sk_buff *skb;
293 size_t len;
294
295 if (!netlink_has_listeners(uevent_sock, 1))
296 continue;
297
298 /* allocate message with the maximum possible size */
299 len = strlen(action_string) + strlen(devpath) + 2;
300 skb = alloc_skb(len + env->buflen, GFP_KERNEL);
301 if (skb) {
302 char *scratch;
303
304 /* add header */
305 scratch = skb_put(skb, len);
306 sprintf(scratch, "%s@%s", action_string, devpath);
307
308 /* copy keys to our continuous event payload buffer */
309 for (i = 0; i < env->envp_idx; i++) {
310 len = strlen(env->envp[i]) + 1;
311 scratch = skb_put(skb, len);
312 strcpy(scratch, env->envp[i]);
313 }
314
315 NETLINK_CB(skb).dst_group = 1;
316 retval = netlink_broadcast_filtered(uevent_sock, skb,
317 0, 1, GFP_KERNEL,
318 kobj_bcast_filter,
319 kobj);
320 /* ENOBUFS should be handled in userspace */
321 if (retval == -ENOBUFS || retval == -ESRCH)
322 retval = 0;
323 } else
324 retval = -ENOMEM;
325 }
326 #endif
327 mutex_unlock(&uevent_sock_mutex);
328
329 #ifdef CONFIG_UEVENT_HELPER
330 /* call uevent_helper, usually only enabled during early boot */
331 if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
332 struct subprocess_info *info;
333
334 retval = add_uevent_var(env, "HOME=/");
335 if (retval)
336 goto exit;
337 retval = add_uevent_var(env,
338 "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
339 if (retval)
340 goto exit;
341 retval = init_uevent_argv(env, subsystem);
342 if (retval)
343 goto exit;
344
345 retval = -ENOMEM;
346 info = call_usermodehelper_setup(env->argv[0], env->argv,
347 env->envp, GFP_KERNEL,
348 NULL, cleanup_uevent_env, env);
349 if (info) {
350 retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
351 env = NULL; /* freed by cleanup_uevent_env */
352 }
353 }
354 #endif
355
356 exit:
357 kfree(devpath);
358 kfree(env);
359 return retval;
360 }
retval = add_uevent_var(env, "%s", envp_ext[i]);//将kernel想要发送的状态信息存储起来。
最终是通过netlink_broadcast_filtered向用户空间发送了uevent信息。
那这个信息,接收者是谁呢?
那就在我们health hidl里面了。
hardware\interfaces\health\2.0\default\healthd_common.cpp
static void uevent_event(uint32_t /*epevents*/) {char msg[UEVENT_MSG_LEN + 2];char* cp;int n;n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);if (n <= 0) return;if (n >= UEVENT_MSG_LEN) /* overflow -- discard */return;msg[n] = '\0';msg[n + 1] = '\0';cp = msg;LOGD("healthd_common.cpp uevent_event msg=%s\n", msg);//lgyLOGD("healthd_common.cpp uevent_event cp=%s\n", cp);//lgywhile (*cp) {if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {LOGD("healthd_common.cpp 111111111 SUBSYSTEM cp=%s\n", cp);//lgyhealthd_battery_update();break;}/* advance to after the next \0 */while (*cp++);}
}
通过uevent_kernel_multicast_recv接收kernel广播上来的信息.
再通过判断是不是POWER_SUPPLY_SUBSYSTEM(#define POWER_SUPPLY_SUBSYSTEM "power_supply")这个子系统中的信息,如果是那就执行healthd_battery_update()
static void healthd_battery_update(void) {LOGD("healthd_common.cpp healthd_battery_update lgy 1111\n");//lgyHealth::getImplementation()->update();
}
然后调用health.cpp中的update()
Return<Result> Health::update() {LOGD("health.cpp update lgy 11111");if (!healthd_mode_ops || !healthd_mode_ops->battery_update) {LOG(WARNING) << "health@2.0: update: not initialized. "<< "update() should not be called in charger / recovery.";return Result::UNKNOWN;}// Retrieve all information and call healthd_mode_ops->battery_update, which calls// notifyListeners.bool chargerOnline = battery_monitor_->update();// adjust uevent / wakealarm periodshealthd_battery_update_internal(chargerOnline);return Result::SUCCESS;
}
bool chargerOnline = battery_monitor_->update();
201 bool BatteryMonitor::update(void) {
202 bool logthis;
203
204 initBatteryProperties(&props);
205
206 if (!mHealthdConfig->batteryPresentPath.isEmpty())
207 props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
208 else
209 props.batteryPresent = mBatteryDevicePresent;
210
211 props.batteryLevel = mBatteryFixedCapacity ?
212 mBatteryFixedCapacity :
213 getIntField(mHealthdConfig->batteryCapacityPath);
214 props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
215
216 if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
217 props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath) / 1000;
218
219 if (!mHealthdConfig->batteryFullChargePath.isEmpty())
220 props.batteryFullCharge = getIntField(mHealthdConfig->batteryFullChargePath);
221
222 if (!mHealthdConfig->batteryCycleCountPath.isEmpty())
223 props.batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);
224
225 if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
226 props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);
227
228 props.batteryTemperature = mBatteryFixedTemperature ?
229 mBatteryFixedTemperature :
230 getIntField(mHealthdConfig->batteryTemperaturePath);
231
232 std::string buf;
233
234 if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
235 props.batteryStatus = getBatteryStatus(buf.c_str());
236
237 if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
238 props.batteryHealth = getBatteryHealth(buf.c_str());
239
240 if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
241 props.batteryTechnology = String8(buf.c_str());
242
243 unsigned int i;
244 double MaxPower = 0;
245
246 for (i = 0; i < mChargerNames.size(); i++) {
247 String8 path;
248 path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
249 mChargerNames[i].string());
250 if (getIntField(path)) {
251 path.clear();
252 path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
253 mChargerNames[i].string());
254 switch(readPowerSupplyType(path)) {
255 case ANDROID_POWER_SUPPLY_TYPE_AC:
256 props.chargerAcOnline = true;
257 break;
258 case ANDROID_POWER_SUPPLY_TYPE_USB:
259 props.chargerUsbOnline = true;
260 break;
261 case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
262 props.chargerWirelessOnline = true;
263 break;
264 default:
265 KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
266 mChargerNames[i].string());
267 }
268 path.clear();
269 path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
270 mChargerNames[i].string());
271 int ChargingCurrent =
272 (access(path.string(), R_OK) == 0) ? getIntField(path) : 0;
273
274 path.clear();
275 path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH,
276 mChargerNames[i].string());
277
278 int ChargingVoltage =
279 (access(path.string(), R_OK) == 0) ? getIntField(path) :
280 DEFAULT_VBUS_VOLTAGE;
281
282 double power = ((double)ChargingCurrent / MILLION) *
283 ((double)ChargingVoltage / MILLION);
284 if (MaxPower < power) {
285 props.maxChargingCurrent = ChargingCurrent;
286 props.maxChargingVoltage = ChargingVoltage;
287 MaxPower = power;
288 }
289 }
290 }
291
292 logthis = !healthd_board_battery_update(&props);
293
294 if (logthis) {
295 char dmesgline[256];
296 size_t len;
297 if (props.batteryPresent) {
298 snprintf(dmesgline, sizeof(dmesgline),
299 "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
300 props.batteryLevel, props.batteryVoltage,
301 props.batteryTemperature < 0 ? "-" : "",
302 abs(props.batteryTemperature / 10),
303 abs(props.batteryTemperature % 10), props.batteryHealth,
304 props.batteryStatus);
305
306 len = strlen(dmesgline);
307 if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
308 len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
309 " c=%d", props.batteryCurrent);
310 }
311
312 if (!mHealthdConfig->batteryFullChargePath.isEmpty()) {
313 len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
314 " fc=%d", props.batteryFullCharge);
315 }
316
317 if (!mHealthdConfig->batteryCycleCountPath.isEmpty()) {
318 len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
319 " cc=%d", props.batteryCycleCount);
320 }
321 } else {
322 len = snprintf(dmesgline, sizeof(dmesgline),
323 "battery none");
324 }
325
326 snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s",
327 props.chargerAcOnline ? "a" : "",
328 props.chargerUsbOnline ? "u" : "",
329 props.chargerWirelessOnline ? "w" : "");
330
331 KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);
332 }
333
334 healthd_mode_ops->battery_update(&props);
335 return props.chargerAcOnline | props.chargerUsbOnline |
336 props.chargerWirelessOnline;
337 }
从文件节点中拿到数据,然后调用healthd_mode_ops->battery_update(&props);
healthd_mode_ops是什么呢?
hardware/interfaces/health/2.0/utils/libhealthservice/HealthServiceCommon.cpp
healthd_mode_service_2_0_ops
1 void healthd_mode_service_2_0_battery_update(struct android::BatteryProperties* prop) {
72 HealthInfo info;
73 convertToHealthInfo(prop, info.legacy);
74 Health::getImplementation()->notifyListeners(&info);
75 }
76
77 static struct healthd_mode_ops healthd_mode_service_2_0_ops = {
78 .init = healthd_mode_service_2_0_init,
79 .preparetowait = healthd_mode_service_2_0_preparetowait,
80 .heartbeat = healthd_mode_service_2_0_heartbeat,
81 .battery_update = healthd_mode_service_2_0_battery_update,
82 };
83
84 int health_service_main(const char* instance) {
85 gInstanceName = instance;
86 if (gInstanceName.empty()) {
87 gInstanceName = "default";
88 }
89 healthd_mode_ops = &healthd_mode_service_2_0_ops;
90 LOG(INFO) << LOG_TAG << gInstanceName << ": Hal starting main loop...";
91 return healthd_main();
92 }
所以healthd_mode_ops->battery_update(&props)其实调用的就是healthd_mode_service_2_0_battery_update();
Health::getImplementation()->notifyListeners(&info);这个notifyListeners又是谁呢?
void Health::notifyListeners(HealthInfo* healthInfo) {LOGD("Health.cpp notifyListeners lgy ");std::vector<StorageInfo> info;get_storage_info(info);std::vector<DiskStats> stats;get_disk_stats(stats);int32_t currentAvg = 0;struct BatteryProperty prop;status_t ret = battery_monitor_->getProperty(BATTERY_PROP_CURRENT_AVG, &prop);if (ret == OK) {currentAvg = static_cast<int32_t>(prop.valueInt64);}healthInfo->batteryCurrentAverage = currentAvg;healthInfo->diskStats = stats;healthInfo->storageInfos = info;std::lock_guard<std::mutex> _lock(callbacks_lock_);for (auto it = callbacks_.begin(); it != callbacks_.end();) {auto ret = (*it)->healthInfoChanged(*healthInfo);if (!ret.isOk() && ret.isDeadObject()) {it = callbacks_.erase(it);} else {++it;}}
}
在这里调用回调函数 auto ret = (*it)->healthInfoChanged(*healthInfo);通知上层了。
23 interface IHealthInfoCallback {
24 /**
25 * An implementation of IHealthInfoBus must call healthInfoChanged on all
26 * registered callbacks after health info changes.
27 * @param info the updated HealthInfo
28 */
29 oneway healthInfoChanged(HealthInfo info);
30 };
在framework里使用health这个hidl服务 和 HealthHalCallback
frameworks/base/services/core/java/com/android/server/BatteryService.java
import android.hardware.health.V2_0.IHealth;
import android.hardware.health.V2_0.IHealthInfoCallback;
1096 private final class HealthHalCallback extends IHealthInfoCallback.Stub
1097 implements HealthServiceWrapper.Callback {
1098 @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
1099 BatteryService.this.update(props);
1100 }
1101 // on new service registered
1102 @Override public void onRegistration(IHealth oldService, IHealth newService,
1103 String instance) {
1104 if (newService == null) return;
1105
1106 traceBegin("HealthUnregisterCallback");
1107 try {
1108 if (oldService != null) {
1109 int r = oldService.unregisterCallback(this);
1110 if (r != Result.SUCCESS) {
1111 Slog.w(TAG, "health: cannot unregister previous callback: " +
1112 Result.toString(r));
1113 }
1114 }
1115 } catch (RemoteException ex) {
1116 Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
1117 + ex.getMessage());
1118 } finally {
1119 traceEnd();
1120 }
1121
1122 traceBegin("HealthRegisterCallback");
1123 try {
1124 int r = newService.registerCallback(this);
1125 if (r != Result.SUCCESS) {
1126 Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
1127 return;
1128 }
1129 // registerCallback does NOT guarantee that update is called
1130 // immediately, so request a manual update here.
1131 newService.update();
1132 } catch (RemoteException ex) {
1133 Slog.e(TAG, "health: cannot register callback (transaction error): "
1134 + ex.getMessage());
1135 } finally {
1136 traceEnd();
1137 }
1138 }
1139 }
最后调用的是 BatteryService.this.update(props)更新电量并发送ACTION_BATTERY_CHANGED广播。
update()-->processValuesLocked()-->sendBatteryChangedIntentLocked()
final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL));
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp uevent_event msg=change@/devices/platform/battery/power_supply/battery
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp uevent_event cp=change@/devices/platform/battery/power_supply/battery
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp 111111111 SUBSYSTEM cp=SUBSYSTEM=power_supply
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp healthd_battery_update lgy 1111
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: health.cpp update lgy 11111
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: BatteryMonitor.cpp BatteryMonitor::update
2019-07-29 10:39:47.048 338-342/? D/LGY_HAL: NetlinkHandler::onEvent subsys = p
2019-07-29 10:39:47.056 358-358/? D/LGY_HAL: HealthServiceCommon.cpp healthd_mode_service_2_0_battery_update lgy 111
2019-07-29 10:39:47.056 358-358/? D/LGY_HAL: Health.cpp notifyListeners lgy
2019-07-29 10:39:47.057 861-1080/system_process D/lgy_debug: HealthHalCallback 1111111111111
2019-07-29 10:39:47.058 861-1080/system_process D/lgy_debug: HealthInfoUpdate update 222222222222
知其然也要知其所以然---Kernel上报电量UEvent事件流程分析相关推荐
- 知其然也知其所以然,Redis笔记总结:核心原理与应用实践
所谓「授人以鱼不如授人以渔」这份笔记的初衷和目标就是帮助后端开发者较为深入的理解Redis背后的原理和实践经验,做到知其然也知其所以然,为未来进阶成长为架构师做好准备. 笔记内容介绍 : Redis基 ...
- 平安科技美国研究院院长韩玫:AI赋能传统行业,要知其然也知其所以然
来源: Robinly 数据猿官网 | www.datayuan.cn 今日头条丨一点资讯丨腾讯丨搜狐丨网易丨凤凰丨阿里UC大鱼丨新浪微博丨新浪看点丨百度百家丨博客中国丨趣头条丨腾讯云·云+社区 本期 ...
- 【转】可解释推荐系统:知其然,知其所以然
原文标题:"可解释推荐系统:知其然,知其所以然" 原文地址:可解释推荐系统:知其然,知其所以然 又是需要学习的地方 近几年,人工智能的可解释性问题受到了来自政府.工业界和学术界的广 ...
- 感想篇:7)知其然与知其所以然,KnowHow与KnowWhy
本章目的:探究--知其然与知其所以然,KnowHow与KnowWhy. 1.Know-How体系与代价: 100多年的汽车研发历史表明,企业只有开发过两代车以上才能逐步建立和完善Know-How体系. ...
- 知其然而不知其所以然
知其然而不知其所以然 [词 目]知其然而不知其所以然 [读 音]zhī qí rán ér bù zhī qí suǒ yǐ rán [释 义]然:这样,如此.知道是这样,但不知道为什么是这样.只知道 ...
- 苏州蜗牛面试感想 ------- 知其然而不知其所以然
今天去面试了..公司环境不错..挺个性的...氛围也不错... 两个面试管 给我感觉一个比较随和 另外一个比较严肃... 弄的我蛮紧张的 由于公司是游戏开发 问了一些线程. 程序设计,,还有设计模式 ...
- No.2第一章 启航 | Flink 知其然,知其所以然
|文末 点击[在看]留言 反馈 | Flink知其然部分共有三个章节,其中 第一章 启航 部分会 从Flink的应用场景切入,让大家清楚的判断自己的业务场景是否适合使用Flink,同时介绍Flink的 ...
- No.3 课程综述 | Flink 知其然,知其所以然
|文末 点击[在看]留言 反馈 | 本节会向大家介绍本课的内容是如何安排的,每个部分要达成的目标和每个具体章节的内容细节摘要!让大家在进行课程学习之前对课程有一个整体了解. 课程综述 <Apac ...
- No.1 序 | Flink 知其然,知其所以然
课程综述 <Apache Flink 知其然,知其所以然>课程,在内容上会先对Flink整体架构和所适用的场景做一个基础介绍,让你对Flink有一个整体的认识!然后对核心概念进行详细介绍, ...
最新文章
- python 2.6.6安装MySQL-python模块
- Java swing 如何将一个按钮放置到弹出框框的任意位置?(Absolute layout 布局的使用)...
- 2015.1.3 让CombBox自动弹出下拉框
- 一文彻底理解Java单元测试
- ORACLE 10G以后的ORDER BY操作优化
- PAT (Basic Level) 1044 火星数字(模拟)
- mysql 查询执行过程_深入浅出Mysql(一)——sql查询执行过程
- qdir安装 多窗口资源管理软件
- SQL-必知必会-触发器
- 06-人脸识别-MTCNN的感性认识(转载)
- Informix 11.5 SQL 语句性能监控方法及实现
- Ruby设计模式透析之 —— 组合(Composite)
- 21. Magento 创建新闻模块(2)
- WAN killer
- SPSS入门教程—问卷的信度量化分析
- 白光LED焊接技术要求
- OpenTCS打造移动机器人交通管制系统(十)
- TCP三次握手第三次握手时ACK丢失怎么办
- mysql 显示表情符号_mysql 支持emoji 表情字符的解决方法。
- 域渗透非约束委派Spooler
热门文章
- HTML语言语法大全
- 【调剂】厦门大学信息学院(特色化示范性软件学学院)2023年硕士研究生复试通知...
- 自动化运维之k8s——Kubernetes集群部署、pod、service微服务、kubernetes网络通信
- 科蒂斯控制器故障代码_科蒂斯控制器故障代码指示
- 钉钉:开放不是玩流量
- Android APK瘦身
- (1)stata的基本使用--数据导入与处理
- denied 登陆后access_Python网络爬虫实战:世纪佳缘爬取近6万条小姐姐数据后发现惊天秘密...
- 要帮助年轻人实现“Vlog自由”,华为nova8凭什么?
- 会会网大讲堂——中国会展业“发展史”