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));

以下是debug整个流程的log;

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事件流程分析相关推荐

  1. 知其然也知其所以然,Redis笔记总结:核心原理与应用实践

    所谓「授人以鱼不如授人以渔」这份笔记的初衷和目标就是帮助后端开发者较为深入的理解Redis背后的原理和实践经验,做到知其然也知其所以然,为未来进阶成长为架构师做好准备. 笔记内容介绍 : Redis基 ...

  2. 平安科技美国研究院院长韩玫:AI赋能传统行业,要知其然也知其所以然

    来源: Robinly 数据猿官网 | www.datayuan.cn 今日头条丨一点资讯丨腾讯丨搜狐丨网易丨凤凰丨阿里UC大鱼丨新浪微博丨新浪看点丨百度百家丨博客中国丨趣头条丨腾讯云·云+社区 本期 ...

  3. 【转】可解释推荐系统:知其然,知其所以然

    原文标题:"可解释推荐系统:知其然,知其所以然" 原文地址:可解释推荐系统:知其然,知其所以然 又是需要学习的地方 近几年,人工智能的可解释性问题受到了来自政府.工业界和学术界的广 ...

  4. 感想篇:7)知其然与知其所以然,KnowHow与KnowWhy

    本章目的:探究--知其然与知其所以然,KnowHow与KnowWhy. 1.Know-How体系与代价: 100多年的汽车研发历史表明,企业只有开发过两代车以上才能逐步建立和完善Know-How体系. ...

  5. 知其然而不知其所以然

    知其然而不知其所以然 [词 目]知其然而不知其所以然 [读 音]zhī qí rán ér bù zhī qí suǒ yǐ rán [释 义]然:这样,如此.知道是这样,但不知道为什么是这样.只知道 ...

  6. 苏州蜗牛面试感想 ------- 知其然而不知其所以然

    今天去面试了..公司环境不错..挺个性的...氛围也不错... 两个面试管  给我感觉一个比较随和 另外一个比较严肃... 弄的我蛮紧张的 由于公司是游戏开发 问了一些线程. 程序设计,,还有设计模式 ...

  7. No.2第一章 启航 | Flink 知其然,知其所以然

    |文末 点击[在看]留言 反馈 | Flink知其然部分共有三个章节,其中 第一章 启航 部分会 从Flink的应用场景切入,让大家清楚的判断自己的业务场景是否适合使用Flink,同时介绍Flink的 ...

  8. No.3 课程综述 | Flink 知其然,知其所以然

    |文末 点击[在看]留言 反馈 | 本节会向大家介绍本课的内容是如何安排的,每个部分要达成的目标和每个具体章节的内容细节摘要!让大家在进行课程学习之前对课程有一个整体了解. 课程综述 <Apac ...

  9. No.1 序 | Flink 知其然,知其所以然

    课程综述 <Apache Flink 知其然,知其所以然>课程,在内容上会先对Flink整体架构和所适用的场景做一个基础介绍,让你对Flink有一个整体的认识!然后对核心概念进行详细介绍, ...

最新文章

  1. python 2.6.6安装MySQL-python模块
  2. Java swing 如何将一个按钮放置到弹出框框的任意位置?(Absolute layout 布局的使用)...
  3. 2015.1.3 让CombBox自动弹出下拉框
  4. 一文彻底理解Java单元测试
  5. ORACLE 10G以后的ORDER BY操作优化
  6. PAT (Basic Level) 1044 火星数字(模拟)
  7. mysql 查询执行过程_深入浅出Mysql(一)——sql查询执行过程
  8. qdir安装 多窗口资源管理软件
  9. SQL-必知必会-触发器
  10. 06-人脸识别-MTCNN的感性认识(转载)
  11. Informix 11.5 SQL 语句性能监控方法及实现
  12. Ruby设计模式透析之 —— 组合(Composite)
  13. 21. Magento 创建新闻模块(2)
  14. WAN killer
  15. SPSS入门教程—问卷的信度量化分析
  16. 白光LED焊接技术要求
  17. OpenTCS打造移动机器人交通管制系统(十)
  18. TCP三次握手第三次握手时ACK丢失怎么办
  19. mysql 显示表情符号_mysql 支持emoji 表情字符的解决方法。
  20. 域渗透非约束委派Spooler

热门文章

  1. HTML语言语法大全
  2. 【调剂】厦门大学信息学院(特色化示范性软件学学院)2023年硕士研究生复试通知...
  3. 自动化运维之k8s——Kubernetes集群部署、pod、service微服务、kubernetes网络通信
  4. 科蒂斯控制器故障代码_科蒂斯控制器故障代码指示
  5. 钉钉:开放不是玩流量
  6. Android APK瘦身
  7. (1)stata的基本使用--数据导入与处理
  8. denied 登陆后access_Python网络爬虫实战:世纪佳缘爬取近6万条小姐姐数据后发现惊天秘密...
  9. 要帮助年轻人实现“Vlog自由”,华为nova8凭什么?
  10. 会会网大讲堂——中国会展业“发展史”