第一种发包逻辑是

ath9k_tx (main.c) -> ath9k_tx_start (xmit.c) -> ath_tx_prepare (xmit.c)
-> ath_tx_send_normal (xmit.c) -> ath_tx_txqaddbuf (xmit.c) ->
ath9k_hw_txstart (mac.c)

这一套流程恰恰就是笔者在 Learning Part III 中总结过的,ath9k driver 将上层传下来的包放入 hardware buffer 进行发送的过程。第二种方法就着重于 transmission tasklet and interrupts。

ath9k_tasklet() (main.c) -> ath_tx_edma_tasklet() (xmit.c) ->
ath_tx_process_buffer (xmit.c) -> ath_tx_complete_aggr (xmit.c) (if ampdu
is used) -> ath_tx_complete_buf (xmit.c) -> ath_tx_complete (xmit.c)
ath_txq_skb_done (xmit.c)

由上面的流程可见,ath9k driver 会对已经发送出去的 packet 进行一系列处理,例如释放内存。那么 ath9k driver 是如何调整发送速率的呢?这里面其实应用了 ath9k transmission rate adjustment 功能。众所周知,在 802.11 通信协议下,发送端会基于信道状况来动态的调整自己的 transmission rate。因为 802.11 通信协议大多采取 CSMA/CA 机制,如果一个 station 在明知道信道繁忙的情况下还不降低自己的 transmission rate,那么就只会增加信道干扰进而极大地降低 network throughput。在本篇文章中,笔者汇总了 ath9k transmission rate adjustment 功能中涉及的结构体和 functions,下一篇文章中再讨论 transmission tasklet and interrupts。

Transmission adjustment: Structures

在 transmission adjustment process 中发挥重要作用的结构体汇总如下:

ieee80211_sta

struc ieee80211_sta 储存了发送对象的基本信息,包括它支持的频率段(决定了最高 transmission rate),mac 地址和 capacibility。

/include/net/mac80211.h
/*** struct ieee80211_sta - station table entry** A station table entry represents a station we are possibly* communicating with. Since stations are RCU-managed in* mac80211, any ieee80211_sta pointer you get access to must* either be protected by rcu_read_lock() explicitly or implicitly,* or you must take good care to not use such a pointer after a* call to your sta_remove callback that removed it.** @addr: MAC address* @aid: AID we assigned to the station if we're an AP* @supp_rates: Bitmap of supported rates (per band)* @ht_cap: HT capabilities of this STA; restricted to our own TX capabilities* @drv_priv: data area for driver use, will always be aligned to* sizeof(void *), size is determined in hw information.*/
struct ieee80211_sta {u32 supp_rates[IEEE80211_NUM_BANDS]; //supported frequency bandsu8 addr[ETH_ALEN];u16 aid;struct ieee80211_sta_ht_cap ht_cap;/* must be last */u8 drv_priv[0] __attribute__((__aligned__(sizeof(void *))));
};

struct ath_rate_priv

这个 structure 定义了这台设备的 priv (private?) rate control information。

/drivers/net/wireless/ath/ath9k/rc.h
/*** struct ath_rate_priv - Rate Control priv data* @state: RC state* @probe_rate: rate we are probing at* @probe_time: msec timestamp for last probe* @hw_maxretry_pktcnt: num of packets since we got HW max retry error* @max_valid_rate: maximum number of valid rate* @per_down_time: msec timestamp for last PER down step* @valid_phy_ratecnt: valid rate count* @rate_max_phy: phy index for the max rate* @per: PER for every valid rate in %* @probe_interval: interval for ratectrl to probe for other rates* @ht_cap: HT capabilities* @neg_rates: Negotatied rates* @neg_ht_rates: Negotiated HT rates*/
struct ath_rate_priv {u8 rate_table_size;u8 probe_rate;u8 hw_maxretry_pktcnt;u8 max_valid_rate;u8 valid_rate_index[RATE_TABLE_SIZE];u8 ht_cap;u8 valid_phy_ratecnt[WLAN_RC_PHY_MAX];u8 valid_phy_rateidx[WLAN_RC_PHY_MAX][RATE_TABLE_SIZE];u8 rate_max_phy;u8 per[RATE_TABLE_SIZE];u32 probe_time;u32 per_down_time;u32 probe_interval;struct ath_rateset neg_rates;struct ath_rateset neg_ht_rates;const struct ath_rate_table *rate_table;struct dentry *debugfs_rcstats;struct ath_rc_stats rcstats[RATE_TABLE_SIZE];
};

ieee80211_tx_info

这个 structure 定义在一个 packet 的 control buffer 里面,可见其重要性。ieee80211_tx_info 结构相对复杂,包含 control & status units,对 TX Condition 控制和分析有非常重要的作用。对这个结构体的详细分析,请参考 link_2.

/include/net/mac80211.h
/*** struct ieee80211_tx_info - skb transmit information** This structure is placed in skb->cb for three uses:*  (1) mac80211 TX control - mac80211 tells the driver what to do*  (2) driver internal use (if applicable)*  (3) TX status information - driver tells mac80211 what happened** The TX control's sta pointer is only valid during the ->tx call,* it may be NULL.** @flags: transmit info flags, defined above* @band: the band to transmit on (use for checking for races)* @antenna_sel_tx: antenna to use, 0 for automatic diversity* @pad: padding, ignore* @control: union for control data* @status: union for status data* @driver_data: array of driver_data pointers* @ampdu_ack_len: number of acked aggregated frames.*     relevant only if IEEE80211_TX_STAT_AMPDU was set.* @ampdu_len: number of aggregated frames.*   relevant only if IEEE80211_TX_STAT_AMPDU was set.* @ack_signal: signal strength of the ACK frame*/
struct ieee80211_tx_info {/* common information */u32 flags;u8 band;u8 antenna_sel_tx;/* 2 byte hole */u8 pad[2];union {struct {union {/* rate control */struct {struct ieee80211_tx_rate rates[IEEE80211_TX_MAX_RATES];s8 rts_cts_rate_idx;};/* only needed before rate control */unsigned long jiffies;};/* NB: vif can be NULL for injected frames */struct ieee80211_vif *vif;struct ieee80211_key_conf *hw_key;struct ieee80211_sta *sta;} control;struct {struct ieee80211_tx_rate rates[IEEE80211_TX_MAX_RATES];u8 ampdu_ack_len;int ack_signal;u8 ampdu_len;/* 15 bytes free */} status;struct {struct ieee80211_tx_rate driver_rates[IEEE80211_TX_MAX_RATES];void *rate_driver_data[IEEE80211_TX_INFO_RATE_DRIVER_DATA_SIZE / sizeof(void *)];};void *driver_data[IEEE80211_TX_INFO_DRIVER_DATA_SIZE / sizeof(void *)];};
};

ieee80211_tx_rate

这个 structure 简单直接。它在 transmission 开始前定义了这个 packet 可以在不同的 transmission rate 上最多重传几次。在 transmission 结束之后,ath9k driver 会根据收发情况去改写 ieee80211_tx_rate 中的数值从而表明这个 packet 是在哪一个 transmission rate 上发送几次之后才成功的。更加详细的介绍还请参考link_2.

/include/net/mac80211.h
/*** struct ieee80211_tx_rate - rate selection/status** @idx: rate index to attempt to send with* @flags: rate control flags (&enum mac80211_rate_control_flags)* @count: number of tries in this rate before going to the next rate** A value of -1 for @idx indicates an invalid rate and, if used* in an array of retry rates, that no more rates should be tried.** When used for transmit status reporting, the driver should* always report the rate along with the flags it used.** &struct ieee80211_tx_info contains an array of these structs* in the control information, and it will be filled by the rate* control algorithm according to what should be sent. For example,* if this array contains, in the format { <idx>, <count> } the* information*    { 3, 2 }, { 2, 2 }, { 1, 4 }, { -1, 0 }, { -1, 0 }* then this means that the frame should be transmitted* up to twice at rate 3, up to twice at rate 2, and up to four* times at rate 1 if it doesn't get acknowledged. Say it gets* acknowledged by the peer after the fifth attempt, the status* information should then contain*   { 3, 2 }, { 2, 2 }, { 1, 1 }, { -1, 0 } ...* since it was transmitted twice at rate 3, twice at rate 2* and once at rate 1 after which we received an acknowledgement.*/
struct ieee80211_tx_rate {s8 idx;u8 count;u8 flags;
} __packed;

ieee80211_tx_rate_control

这个 structure 广泛的应用于 rc algortithms (rc.c)。他详细的规定了除了 transmission rate 以外的,控制过程中十分重要的 structures。

/include/net/mac80211.h
/*** struct ieee80211_tx_rate_control - rate control information for/from RC algo** @hw: The hardware the algorithm is invoked for.* @sband: The band this frame is being transmitted on.* @bss_conf: the current BSS configuration* @reported_rate: The rate control algorithm can fill this in to indicate*   which rate should be reported to userspace as the current rate and* used for rate calculations in the mesh network.* @rts: whether RTS will be used for this frame because it is longer than the*  RTS threshold* @short_preamble: whether mac80211 will request short-preamble transmission* if the selected rate supports it* @max_rate_idx: user-requested maximum rate (not MCS for now)*    (deprecated; this will be removed once drivers get updated to use*  rate_idx_mask)* @rate_idx_mask: user-requested rate mask (not MCS for now)* @skb: the skb that will be transmitted, the control information in it needs*  to be filled in* @bss: whether this frame is sent out in AP or IBSS mode*/
struct ieee80211_tx_rate_control {struct ieee80211_hw *hw;struct ieee80211_supported_band *sband;struct ieee80211_bss_conf *bss_conf;struct sk_buff *skb;struct ieee80211_tx_rate reported_rate;bool rts, short_preamble;u8 max_rate_idx;u32 rate_idx_mask;bool bss;
};

Transmission adjustment: Functions

重点介绍三个 functions: ath_rate_init、ath_get_rate、ath_tx_status。笔者的学习基于这篇博客: link_3。 另外介绍一下发送端是如何确认一个 packet 有没有被成功接受的方法

ath_rate_init

该 function 的作用为起始化 rate control 算法,source code 如下:

/ath9k/rc.c
static void ath_rate_init(void *priv, struct ieee80211_supported_band *sband,struct cfg80211_chan_def *chandef,struct ieee80211_sta *sta, void *priv_sta)
{struct ath_softc *sc = priv;struct ath_common *common = ath9k_hw_common(sc->sc_ah);struct ath_rate_priv *ath_rc_priv = priv_sta;int i, j = 0;u32 rate_flags = ieee80211_chandef_rate_flags(&sc->hw->conf.chandef);for (i = 0; i < sband->n_bitrates; i++) { if (sta->supp_rates[sband->band] & BIT(i)) {// Check which rates are supported by hardwareif ((rate_flags & sband->bitrates[i].flags)!= rate_flags)continue;ath_rc_priv->neg_rates.rs_rates[j]= (sband->bitrates[i].bitrate * 2) / 10;j++;}}ath_rc_priv->neg_rates.rs_nrates = j;if (sta->ht_cap.ht_supported) {// Which mcs rates can be supported, add them to ath_rc_privfor (i = 0, j = 0; i < 77; i++) {if (sta->ht_cap.mcs.rx_mask[i/8] & (1<<(i%8)))ath_rc_priv->neg_ht_rates.rs_rates[j++] = i;if (j == ATH_RATE_MAX)break;}ath_rc_priv->neg_ht_rates.rs_nrates = j;}ath_rc_priv->rate_table = ath_choose_rate_table(sc, sband->band,sta->ht_cap.ht_supported);if (!ath_rc_priv->rate_table) {ath_err(common, "No rate table chosen\n");return;}ath_rc_priv->ht_cap = ath_rc_build_ht_caps(sc, sta);ath_rc_init(sc, priv_sta);
}

ath_get_rate

这个 function 用来在传输结束后去改写 structure ieee80211_tx_rate 中的内容。

/ath9k/rc.h
static void ath_get_rate(void *priv, struct ieee80211_sta *sta, void *priv_sta,struct ieee80211_tx_rate_control *txrc)
// This function does not transmit a packet, it just sets re-transmit count etc to the ieee80211_tx_rate structure
// for future transmissions
{struct ath_softc *sc = priv;struct ath_rate_priv *ath_rc_priv = priv_sta;const struct ath_rate_table *rate_table;struct sk_buff *skb = txrc->skb;    // txrc structure includes the packet will be transmittedstruct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);struct ieee80211_tx_rate *rates = tx_info->control.rates;struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;__le16 fc = hdr->frame_control;u8 try_per_rate, i = 0, rix;int is_probe = 0;if (rate_control_send_low(sta, priv_sta, txrc)) //rate_control_send_low - helper for drivers for management (no destination) /no-ack packets /*Rate control algorithms that agree to use the lowest rate to send management frames and NO_ACK data withthe respective hw retries should use this in the beginning of their mac80211 get_rate callback. If true is returned the rate control can simply return. If false is returned we guarantee that sta and sta and priv_sta is not null.*/return;/** For Multi Rate Retry (MRR) we use a different number of* retry attempt counts. This ends up looking like this:** MRR[0] = 4 ( If the packet is a probe, set MRR[0] just attempts 4 times in the ieee80211_tx_rate structure.)* MRR[1] = 4* MRR[2] = 4* MRR[3] = 8**/try_per_rate = 4;rate_table = ath_rc_priv->rate_table;rix = ath_rc_get_highest_rix(ath_rc_priv, &is_probe); //1. determine whether it is a probe or not//2. Based on the PER,determines the current//   maximized data rate as rix.if (conf_is_ht(&sc->hw->conf) &&(sta->ht_cap.cap & IEEE80211_HT_CAP_LDPC_CODING))tx_info->flags |= IEEE80211_TX_CTL_LDPC;if (conf_is_ht(&sc->hw->conf) &&(sta->ht_cap.cap & IEEE80211_HT_CAP_TX_STBC))tx_info->flags |= (1 << IEEE80211_TX_CTL_STBC_SHIFT);if (is_probe) {/** Set one try for probe rates. For the* probes don't enable RTS.*/ath_rc_rate_set_series(rate_table, &rates[i++], txrc,1, rix, 0); // Write ieee80211_tx_rate structure (rates) based on txrc/** Get the next tried/allowed rate.* No RTS for the next series after the probe rate.*/ath_rc_get_lower_rix(ath_rc_priv, rix, &rix);ath_rc_rate_set_series(rate_table, &rates[i++], txrc,try_per_rate, rix, 0);tx_info->flags |= IEEE80211_TX_CTL_RATE_CTRL_PROBE;} else {/** Set the chosen rate. No RTS for first series entry.*/ath_rc_rate_set_series(rate_table, &rates[i++], txrc,try_per_rate, rix, 0); // If the packet is not probe, then try 4 times for every available rate}for ( ; i < 4; i++) {/** Use twice the number of tries for the last MRR segment.*/if (i + 1 == 4)try_per_rate = 8;ath_rc_get_lower_rix(ath_rc_priv, rix, &rix);/** All other rates in the series have RTS enabled.*/ath_rc_rate_set_series(rate_table, &rates[i], txrc,try_per_rate, rix, 1); // 1 is rtsctsenable, used as a boolean, Set USE_RTS_CTS=1// RTS/CTS (Request To Send / Clear To Send) CSMA/CA }/** NB:Change rate series to enable aggregation when operating* at lower MCS rates. When first rate in series is MCS2* in HT40 @ 2.4GHz, series should look like:** {MCS2, MCS1, MCS0, MCS0}.** When first rate in series is MCS3 in HT20 @ 2.4GHz, series should* look like:** {MCS3, MCS2, MCS1, MCS1}** So, set fourth rate in series to be same as third one for* above conditions.*/if ((sc->hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) &&(conf_is_ht(&sc->hw->conf))) {u8 dot11rate = rate_table->info[rix].dot11rate;u8 phy = rate_table->info[rix].phy;if (i == 4 &&((dot11rate == 2 && phy == WLAN_RC_PHY_HT_40_SS) ||(dot11rate == 3 && phy == WLAN_RC_PHY_HT_20_SS))) {rates[3].idx = rates[2].idx;rates[3].flags = rates[2].flags;}}/** Force hardware to use computed duration for next* fragment by disabling multi-rate retry, which* updates duration based on the multi-rate duration table.** FIXME: Fix duration*/if (ieee80211_has_morefrags(fc) ||(le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG)) {rates[1].count = rates[2].count = rates[3].count = 0;rates[1].idx = rates[2].idx = rates[3].idx = 0;rates[0].count = ATH_TXMAXTRY;}ath_rc_rate_set_rtscts(sc, rate_table, tx_info);
}

ath_tx_status & ath_rc_tx_status

ath_tx_status 首先判断该 packet 是否含有 status info。如果有,使用 ath_rc_tx_status 去读出 status info。

/ath9k/rc.c
static void ath_tx_status(void *priv, struct ieee80211_supported_band *sband,struct ieee80211_sta *sta, void *priv_sta,struct sk_buff *skb)
{struct ath_softc *sc = priv;struct ath_rate_priv *ath_rc_priv = priv_sta;struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;__le16 fc = hdr->frame_control;if (!priv_sta || !ieee80211_is_data(fc))return;/* This packet was aggregated but doesn't carry status info */if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) &&!(tx_info->flags & IEEE80211_TX_STAT_AMPDU))return;if (tx_info->flags & IEEE80211_TX_STAT_TX_FILTERED)return;ath_rc_tx_status(sc, ath_rc_priv, skb);/* Check if aggregation has to be enabled for this tid */if (conf_is_ht(&sc->hw->conf) &&!(skb->protocol == cpu_to_be16(ETH_P_PAE))) {if (ieee80211_is_data_qos(fc) &&skb_get_queue_mapping(skb) != IEEE80211_AC_VO) {u8 *qc, tid;qc = ieee80211_get_qos_ctl(hdr);tid = qc[0] & 0xf;if(ath_tx_aggr_check(sc, sta, tid))ieee80211_start_tx_ba_session(sta, tid, 0);}}
}/ath9k/rc.c
static void ath_rc_tx_status(struct ath_softc *sc,struct ath_rate_priv *ath_rc_priv,struct sk_buff *skb)
{struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);struct ieee80211_tx_rate *rates = tx_info->status.rates;struct ieee80211_tx_rate *rate;int final_ts_idx = 0, xretries = 0, long_retry = 0;u8 flags;u32 i = 0, rix;for (i = 0; i < sc->hw->max_rates; i++) { // Determine the transmission rate in which the packet is transmittedrate = &tx_info->status.rates[i];if (rate->idx < 0 || !rate->count)break;final_ts_idx = i;long_retry = rate->count - 1;}if (!(tx_info->flags & IEEE80211_TX_STAT_ACK))xretries = 1;                        // xretries=1, means that the data frame has not been successfully transmitted/** If the first rate is not the final index, there* are intermediate rate failures to be processed.*/if (final_ts_idx != 0) {for (i = 0; i < final_ts_idx ; i++) {if (rates[i].count != 0 && (rates[i].idx >= 0)) {flags = rates[i].flags;/* If HT40 and we have switched mode from* 40 to 20 => don't update */if ((flags & IEEE80211_TX_RC_40_MHZ_WIDTH) &&!(ath_rc_priv->ht_cap & WLAN_RC_40_FLAG))return;rix = ath_rc_get_rateindex(ath_rc_priv, &rates[i]);ath_rc_update_ht(sc, ath_rc_priv, tx_info,rix, xretries ? 1 : 2,rates[i].count);}}}flags = rates[final_ts_idx].flags;/* If HT40 and we have switched mode from 40 to 20 => don't update */if ((flags & IEEE80211_TX_RC_40_MHZ_WIDTH) &&!(ath_rc_priv->ht_cap & WLAN_RC_40_FLAG))return;rix = ath_rc_get_rateindex(ath_rc_priv, &rates[final_ts_idx]);ath_rc_update_ht(sc, ath_rc_priv, tx_info, rix, xretries, long_retry); // This function is actually used for updating PER ath_debug_stat_rc(ath_rc_priv, rix);
}

Conclusion

本篇文章着重归纳了 ath9k 中的 transmission rate adjustment functions,但并没有很详尽地介绍细节。原因是这些内容在 link_2 和 link_3 中已经得到了很好的阐述,还请读者查阅。下一篇文章中笔者将介绍在发送过程中 ath9k 如何去调用 transmission rate adjustment functions。

ATH9K Driver Learning Part VI: Difference Between Packet Preparation And Transmission Control相关推荐

  1. ATH9K Driver Learning Part II: Important Transmission Functions

    本篇文章将探讨 ath9k driver 中与发包息息相关的 functions.传输途径主要为:ath9k_tx () --> ath_tx_start () --> ath_txq_s ...

  2. ATH9K DRIVER LEARNING PART V: KFIFO

    由于在 ath9k driver 中, 发送的 packet 全部都储存在 kfifo 结构体里.详细的学习 kfifo 相关的知识能帮助用户去监视各个 packet 的收发状况.在这篇文章中笔者汇总 ...

  3. ATH9K Driver Learning Part III: Data packet transmission

    在第二篇文章中,我们发现 function ath_tx_start() 会根据 packet 的种类不同而选择不同的发送方式.根据实验结果,我发现只有在 AP-STATION CONNECTION ...

  4. Learning the Vi Editor, 6th Edition学习笔记(0)

    文本编辑是任何一个计算机系统最普遍的应用之一,而Vi 是最有用的标准文本编辑器之一.我们可以使用Vi创建新的文件或者编辑任意存在的UNIX文本文件. 本书主要分为3部分,由12个章节和5个附录组成,详 ...

  5. 论文笔记008:[CVPR2016]Deep Relative Distance Learning: Tell the Difference Between Similar Vehicles

    摘要 在公共安全领域,监控摄像头的使用日益激增,突显出从大规模图像或视频数据库中搜索车辆的重要性.然而,与行人重识别或人脸识别相比,车辆搜索问题长期以来一直被视觉界研究者所忽视.本文重点研究一个有趣但 ...

  6. Memory-Associated Differential Learning论文及代码解读

    Memory-Associated Differential Learning论文及代码解读 论文来源: 论文PDF: Memory-Associated Differential Learning论 ...

  7. KettleError connecting to database: (using class org.gjt.mm.mysql.Driver)Communications link failure

    先看错误: 错误连接数据库 [JDOrd] : org.pentaho.di.core.exception.KettleDatabaseException: Error occurred while ...

  8. 18 Issues in Current Deep Reinforcement Learning from ZhiHu

    深度强化学习的18个关键问题 from: https://zhuanlan.zhihu.com/p/32153603 85 人赞了该文章 深度强化学习的问题在哪里?未来怎么走?哪些方面可以突破? 这两 ...

  9. NS2 教學手冊 ( NS2 Learning Guide)

    转载自:NS2 教学手册(柯志亨网站资源) NS2 教學手冊 ( NS2 Learning Guide) [快速連結區] My works  中文影音教學區  Q&A for my works ...

最新文章

  1. 全球富豪大洗牌!马斯克登顶世界首富,黄铮国内第三超马云
  2. 监测SQLServer数据库中表的数据变化 方案
  3. JavaScript事件循环探索
  4. SQL Server 2019安装教程
  5. 用python阐释工作量证明(proof of work)
  6. ip打包后如何加入 xilinx_科普!插上USB设备后电脑是怎么识别的呢?
  7. PageHelper 关闭COUNT(0)查询 以及PageHelper 的分页原理分析
  8. F-Stack实现UDP服务端、客户端,并进行吞吐量测试的实现
  9. 【解决方案】ArcGIS License Manager启动失败
  10. 复现 ASPCMS企业建站系统Cookies欺骗漏洞
  11. 涉密计算机怎么更新补丁,windows系统补丁你更新还是不更新?
  12. 计算机网络之应用层(DNS域名系统)
  13. 怎么把raw转换成jpg格式?推荐两个raw转jpg的方法
  14. AutoCAD2016简体中文破解版32位64位下载
  15. 4K秒开,稀缺宝藏影视APP!
  16. uni-app实现微信相机
  17. k8s在华为openeuler搭建
  18. Gitlab----Pipline流水线语法only、except、rules、workflow
  19. 无法启用IE代理,EasyConnect不支持自动检测设置,请手动配置代理服务器的IP和端口后重试
  20. java调用qt生成的dll_在Qt中调用vs2008生成的dll以及lib的方法

热门文章

  1. 学会拒绝摔倒“哭泣”,拒绝接受“溺爱”
  2. 求解,某M1水卡数据计算分析/大神们求指导!
  3. 关于HML要玩物联网这件事 之 CC3200 TCP Client
  4. C语言实现自动出题、单词拼写等功能,附带管理员模式
  5. 教程 | 在Unity中使用 Isometric Tilemap(等距、正交)
  6. [转]十年一覺程設夢[完整版]
  7. ubuntu 下 uml 工具
  8. NAS、SAN、ISCSI存储简单理解
  9. 2019年4月中国编程语言排行榜,java占有率一骑绝尘,python工资领先
  10. elastic-job入门(二)