亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区
Chinaunix
標(biāo)題:
內(nèi)核中的TCP的追蹤分析-16-TCP(IPV4)的客戶端與服務(wù)器端socket連接過(guò)程-3
[打印本頁(yè)]
作者:
qinjiana0786
時(shí)間:
2008-11-21 11:20
標(biāo)題:
內(nèi)核中的TCP的追蹤分析-16-TCP(IPV4)的客戶端與服務(wù)器端socket連接過(guò)程-3
在上面15節(jié)處我們最后跟蹤到了內(nèi)核的tcp_v4_rcv()函數(shù)處,它在/net/ipv4/tcp_ipv4.c中的1601行處,我們分段來(lái)看
int tcp_v4_rcv(struct sk_buff *skb)
{
const struct iphdr *iph;
struct tcphdr *th;
struct sock *sk;
int ret;
if (skb->pkt_type != PACKET_HOST)
goto discard_it;
/* Count it even if it's bad */
TCP_INC_STATS_BH(TCP_MIB_INSEGS);
if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
goto discard_it;
th = tcp_hdr(skb);
if (th->doff sizeof(struct tcphdr) / 4)
goto bad_packet;
if (!pskb_may_pull(skb, th->doff * 4))
goto discard_it;
/* An explanation is required here, I think.
* Packet length and doff are validated by header prediction,
* provided case of th->doff==0 is eliminated.
* So, we defer the checks. */
if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
goto bad_packet;
th = tcp_hdr(skb);
iph = ip_hdr(skb);
TCP_SKB_CB(skb)->seq = ntohl(th->seq);
TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
skb->len - th->doff * 4);
TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
TCP_SKB_CB(skb)->when = 0;
TCP_SKB_CB(skb)->flags = iph->tos;
TCP_SKB_CB(skb)->sacked = 0;
sk = __inet_lookup(dev_net(skb->dev), &tcp_hashinfo, iph->saddr,
th->source, iph->daddr, th->dest, inet_iif(skb));
if (!sk)
goto no_tcp_socket;
函數(shù)開(kāi)頭首先是檢查一下數(shù)據(jù)包的類型是否是PACKET_HOST,即是否是發(fā)給本機(jī)的數(shù)據(jù)包,如果不是就要跳轉(zhuǎn)到標(biāo)號(hào)discard_it處丟掉數(shù)據(jù)包。TCP_INC_STATS_BH是一個(gè)宏
#define TCP_INC_STATS_BH(field) SNMP_INC_STATS_BH(tcp_statistics, field)
#define SNMP_INC_STATS_BH(mib, field) \
(per_cpu_ptr(mib[0], raw_smp_processor_id())->mibs[field]++)
我們這里看到了tcp_statistics它是在/net/ipv4/tcp.c中聲明的
DEFINE_SNMP_STAT(struct tcp_mib, tcp_statistics) __read_mostly;
這里涉及到了SNMP的統(tǒng)計(jì)信息,SNMP(Simple Network Management Protocol,簡(jiǎn)單網(wǎng)絡(luò)管理協(xié)議)的前身是簡(jiǎn)單網(wǎng)關(guān)監(jiān)控協(xié)議(SGMP),用來(lái)對(duì)
通信
線路進(jìn)行管理。具體的解釋請(qǐng)朋友們看
http://baike.baidu.com/view/2899.htm
我們可以在/include/net/snmp.h中看到關(guān)于snmp的這個(gè)宏
#define DEFINE_SNMP_STAT(type, name) \
__typeof__(type) *name[2]
所以上面實(shí)際上是聲明了一個(gè)struct tcp_mib的結(jié)構(gòu)變量tcp_statistics,我們會(huì)在snmp.h中同時(shí)看到
struct tcp_mib {
unsigned long mibs[TCP_MIB_MAX];
} __SNMP_MIB_ALIGN__;
相關(guān)于具體的snmp在內(nèi)核的原理請(qǐng)朋友們參考
http://blog.chinaunix.net/u/12313/showart_172685.html
,我們這里就不再具體分析了,但是從函數(shù)中調(diào)用字面上來(lái)看是增加了tcp_statistics中的mibs關(guān)于TCP_MIB_INSEGS的計(jì)數(shù)。我們繼續(xù)往下看tcp_v4_rcv()函數(shù),代碼中接著調(diào)用了
static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
{
if (likely(len = skb_headlen(skb)))
return 1;
if (unlikely(len > skb->len))
return 0;
return __pskb_pull_tail(skb, len-skb_headlen(skb)) != NULL;
}
很明顯是對(duì)數(shù)據(jù)包頭的長(zhǎng)度檢測(cè),如果數(shù)據(jù)包頭的長(zhǎng)度大于或等于struct tcphdr的tcp的頭結(jié)構(gòu)長(zhǎng)度的話也會(huì)丟掉數(shù)據(jù)包。檢測(cè)長(zhǎng)度通過(guò)后,就會(huì)取得數(shù)據(jù)包skb中的tcp頭結(jié)構(gòu),關(guān)于struct tcphdr我們?cè)诒绢愔械?1節(jié)
http://blog.chinaunix.net/u2/64681/showart.php?id=1415963
已經(jīng)列出了,朋友們可以回過(guò)頭去看一下,代碼中接著對(duì)這個(gè)tcp頭結(jié)構(gòu)進(jìn)行檢測(cè),看是否匹配要求,doff是指示頭長(zhǎng)度的變量值,如果doff小于tcp的標(biāo)準(zhǔn)頭部結(jié)構(gòu)的四分之一就會(huì)跳轉(zhuǎn)到bad_packet標(biāo)號(hào)處,增加tcp_statistics的TCP_MIB_INERRS關(guān)于出錯(cuò)的snmp統(tǒng)計(jì)信息,然后丟掉數(shù)據(jù)包。接下來(lái)再次以doff為參考查看數(shù)據(jù)包頭部是否越界了,接下來(lái)根據(jù)數(shù)據(jù)包的ip頭部和tcp的頭部結(jié)構(gòu)計(jì)算數(shù)據(jù)包中的關(guān)于tcp_skb_cb結(jié)構(gòu)的初始化操作。TCP_SKB_CB宏我們?cè)诒绢惖牡?0節(jié)看到了
http://blog.chinaunix.net/u2/64681/showart.php?id=1414314
這里我們就不具體解釋結(jié)構(gòu)變量的作用和意義了,我和具體的協(xié)議要求相關(guān),我們重點(diǎn)關(guān)心的是追蹤的關(guān)鍵過(guò)程,我們看到函數(shù)接著進(jìn)入了__inet_lookup()函數(shù)中
static inline struct sock *__inet_lookup(struct net *net,
struct inet_hashinfo *hashinfo,
const __be32 saddr, const __be16 sport,
const __be32 daddr, const __be16 dport,
const int dif)
{
u16 hnum = ntohs(dport);
struct sock *sk = __inet_lookup_established(net, hashinfo,
saddr, sport, daddr, hnum, dif);
return sk ? : __inet_lookup_listener(net, hashinfo, daddr, hnum, dif);
}
注意傳遞進(jìn)這個(gè)函數(shù)的參數(shù)除了數(shù)據(jù)包中的信息還有一個(gè)struct inet_hashinfo全局的結(jié)構(gòu)變量tcp_hashinfo,這個(gè)結(jié)構(gòu)在第6節(jié)中看到了
http://blog.chinaunix.net/u2/64681/showart.php?id=1404050
我們看到在__inet_lookup中,首先通過(guò)__inet_lookup_established()函數(shù)來(lái)查找已經(jīng)處于連接的sock結(jié)構(gòu)。
struct sock * __inet_lookup_established(struct net *net,
struct inet_hashinfo *hashinfo,
const __be32 saddr, const __be16 sport,
const __be32 daddr, const u16 hnum,
const int dif)
{
INET_ADDR_COOKIE(acookie, saddr, daddr)
const __portpair ports = INET_COMBINED_PORTS(sport, hnum);
struct sock *sk;
const struct hlist_node *node;
/* Optimize here for direct hit, only listening connections can
* have wildcards anyways.
*/
unsigned int hash = inet_ehashfn(daddr, hnum, saddr, sport);
struct inet_ehash_bucket *head = inet_ehash_bucket(hashinfo, hash);
rwlock_t *lock = inet_ehash_lockp(hashinfo, hash);
prefetch(head->chain.first);
read_lock(lock);
sk_for_each(sk, node, &head->chain) {
if (INET_MATCH(sk, net, hash, acookie,
saddr, daddr, ports, dif))
goto hit; /* You sunk my battleship! */
}
/* Must check for a TIME_WAIT'er before going to listener hash. */
sk_for_each(sk, node, &head->twchain) {
if (INET_TW_MATCH(sk, net, hash, acookie,
saddr, daddr, ports, dif))
goto hit;
}
sk = NULL;
out:
read_unlock(lock);
return sk;
hit:
sock_hold(sk);
goto out;
}
這個(gè)函數(shù)我們暫且不具體解釋了,相關(guān)的hash表和hash桶的內(nèi)容已經(jīng)在第5節(jié)地址綁定那節(jié)中我們看到了,但是如果想看明白上面的代碼還需要了解關(guān)于hash表的過(guò)程,比較原理資料介紹在
http://www.fish888.com/linux-t168886
這篇文章是針對(duì)2.4的內(nèi)核但是其原理對(duì)于朋友們?cè)敿?xì)了解內(nèi)核中的這個(gè)hash表有很好的學(xué)習(xí)作用。我們?nèi)绻貞浺幌略诒O(jiān)聽(tīng)那節(jié)分析中,曾經(jīng)進(jìn)入了__inet_hash()函數(shù)中,將sock掛入到hash表的操作時(shí)
http://blog.chinaunix.net/u2/64681/showart.php?id=1404050
可以在那篇文章中__inet_hash()函數(shù)首先判斷sock的狀態(tài)是否是TCP_LISTEN ,如果不是的話就會(huì)調(diào)用__inet_hash_nolisten(),函數(shù)我們可以在這個(gè)函數(shù)中看到
void __inet_hash_nolisten(struct sock *sk)
{
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
struct hlist_head *list;
rwlock_t *lock;
struct inet_ehash_bucket *head;
BUG_TRAP(sk_unhashed(sk));
sk->sk_hash = inet_sk_ehashfn(sk);
head = inet_ehash_bucket(hashinfo, sk->sk_hash);
list = &head->chain;
lock = inet_ehash_lockp(hashinfo, sk->sk_hash);
write_lock(lock);
__sk_add_node(sk, list);
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
write_unlock(lock);
}
上面是調(diào)用了inet_ehash_bucket()來(lái)掛入我們的全局的tcp_hashinfo中的ehash的雜湊隊(duì)列中了,而__inet_lookup_established()則中再次調(diào)用inet_ehash_bucket()找到這個(gè)ehash雜湊隊(duì)列頭
static inline struct inet_ehash_bucket *inet_ehash_bucket(
struct inet_hashinfo *hashinfo,
unsigned int hash)
{
return &hashinfo->ehash[hash & (hashinfo->ehash_size - 1)];
}
反之我們?cè)诒O(jiān)聽(tīng)那節(jié)文章中講到我們的服務(wù)器socket是啟動(dòng)監(jiān)聽(tīng)過(guò)程的,所以會(huì)在__inet_hash()函數(shù)中掛入tcp_hashinfo的listening_hash雜湊隊(duì)列中。這些過(guò)程請(qǐng)朋友們回過(guò)頭去看一下
http://blog.chinaunix.net/u2/64681/showart.php?id=1404050
那里的服務(wù)器端的監(jiān)聽(tīng)過(guò)程了。我們看__inet_lookup_established()在上面的代碼中會(huì)通過(guò)INET_MATCH來(lái)查找我們?cè)诜⻊?wù)器端匹配數(shù)據(jù)包的要求的地址和端口的sock結(jié)構(gòu),這里我們知道數(shù)據(jù)包已經(jīng)根據(jù)路由改變了相應(yīng)的要查找的地址和端口,這些是在前面章節(jié)中路由過(guò)程中設(shè)置的,只是我們?cè)谀抢餂](méi)有詳細(xì)的對(duì)路由過(guò)程描述。我們以后會(huì)補(bǔ)上這段詳細(xì)的路由過(guò)程,因?yàn)槲覀冊(cè)诒O(jiān)聽(tīng)分析過(guò)程知道了服務(wù)器端的sock處于監(jiān)聽(tīng)狀態(tài)并沒(méi)有掛入ehash雜湊隊(duì)列所以__inet_lookup_established()沒(méi)有找到想要的sock結(jié)構(gòu)。所以回到__inet_lookup()函數(shù)會(huì)執(zhí)行__inet_lookup_listener函數(shù)在listening_hash的監(jiān)聽(tīng)的雜湊隊(duì)列中查找
struct sock *__inet_lookup_listener(struct net *net,
struct inet_hashinfo *hashinfo,
const __be32 daddr, const unsigned short hnum,
const int dif)
{
struct sock *sk = NULL;
const struct hlist_head *head;
read_lock(&hashinfo->lhash_lock);
head = &hashinfo->listening_hash[inet_lhashfn(hnum)];
if (!hlist_empty(head)) {
const struct inet_sock *inet = inet_sk((sk = __sk_head(head)));
if (inet->num == hnum && !sk->sk_node.next &&
(!inet->rcv_saddr || inet->rcv_saddr == daddr) &&
(sk->sk_family == PF_INET || !ipv6_only_sock(sk)) &&
!sk->sk_bound_dev_if && net_eq(sock_net(sk), net))
goto sherry_cache;
sk = inet_lookup_listener_slow(net, head, daddr, hnum, dif);
}
if (sk) {
sherry_cache:
sock_hold(sk);
}
read_unlock(&hashinfo->lhash_lock);
return sk;
}
這個(gè)函數(shù)我們就不用多解釋了,在這個(gè)函數(shù)中我們找到了已經(jīng)創(chuàng)建并處于監(jiān)聽(tīng)狀態(tài)的sock結(jié)構(gòu),這是我們?cè)诘?節(jié)中講述的。在那篇文章結(jié)尾有些未解的問(wèn)題現(xiàn)在已經(jīng)對(duì)接清楚了。
http://blog.chinaunix.net/u2/64681/showart.php?id=1404050
。
回到tcp_v4_rcv()函數(shù)中我們繼續(xù)往下看代碼
process:
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
goto discard_and_relse;
nf_reset(skb);
if (sk_filter(sk, skb))
goto discard_and_relse;
skb->dev = NULL;
bh_lock_sock_nested(sk);
ret = 0;
if (!sock_owned_by_user(sk)) {
#ifdef CONFIG_NET_DMA
struct tcp_sock *tp = tcp_sk(sk);
if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
tp->ucopy.dma_chan = get_softnet_dma();
if (tp->ucopy.dma_chan)
ret = tcp_v4_do_rcv(sk, skb);
else
#endif
{
if (!tcp_prequeue(sk, skb))
ret = tcp_v4_do_rcv(sk, skb);
}
} else
sk_add_backlog(sk, skb);
bh_unlock_sock(sk);
sock_put(sk);
return ret;
no_tcp_socket:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_it;
if (skb->len (th->doff 2) || tcp_checksum_complete(skb)) {
bad_packet:
TCP_INC_STATS_BH(TCP_MIB_INERRS);
} else {
tcp_v4_send_reset(NULL, skb);
}
discard_it:
/* Discard frame. */
kfree_skb(skb);
return 0;
discard_and_relse:
sock_put(sk);
goto discard_it;
do_time_wait:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
inet_twsk_put(inet_twsk(sk));
goto discard_it;
}
if (skb->len (th->doff 2) || tcp_checksum_complete(skb)) {
TCP_INC_STATS_BH(TCP_MIB_INERRS);
inet_twsk_put(inet_twsk(sk));
goto discard_it;
}
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
case TCP_TW_SYN: {
struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
&tcp_hashinfo,
iph->daddr, th->dest,
inet_iif(skb));
if (sk2) {
inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
inet_twsk_put(inet_twsk(sk));
sk = sk2;
goto process;
}
/* Fall through to ACK */
}
case TCP_TW_ACK:
tcp_v4_timewait_ack(sk, skb);
break;
case TCP_TW_RST:
goto no_tcp_socket;
case TCP_TW_SUCCESS:;
}
goto discard_it;
}
接下來(lái)要檢查服務(wù)器端的這個(gè)sock結(jié)構(gòu)是否處于TCP_TIME_WAIT狀態(tài)延時(shí)狀態(tài),如果是的話就要跳到do_time_wait標(biāo)號(hào)處等待了,我們不看這個(gè)過(guò)程了,我們也跳過(guò)對(duì)IPSEC規(guī)則的檢測(cè)函數(shù)xfrm4_policy_check(),象在第8節(jié)
http://blog.chinaunix.net/u2/64681/showart.php?id=1408613
中說(shuō)的那樣,具體的分析放在后續(xù)工作中。接下來(lái)進(jìn)入最關(guān)鍵的過(guò)程
if (!tcp_prequeue(sk, skb))
ret = tcp_v4_do_rcv(sk, skb);
首先是通過(guò)tcp_prequeue()將數(shù)據(jù)包先鏈入tcp_sock結(jié)構(gòu)中的預(yù)備隊(duì)列,這個(gè)函數(shù)的詳細(xì)解釋在
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=138240&page=129&view=collapsed&sb=5&o=all
,我們進(jìn)入tcp_v4_do_rcv()函數(shù)
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
#ifdef CONFIG_TCP_MD5SIG
/*
* We really want to reject the packet as early as possible
* if:
* o We're expecting an MD5'd packet and this is no MD5 tcp option
* o There is an MD5 option and we're not expecting one
*/
if (tcp_v4_inbound_md5_hash(sk, skb))
goto discard;
#endif
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
TCP_CHECK_TIMER(sk);
if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
}
if (skb->len tcp_hdrlen(skb) || tcp_checksum_complete(skb))
goto csum_err;
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
}
TCP_CHECK_TIMER(sk);
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
reset:
tcp_v4_send_reset(rsk, skb);
discard:
kfree_skb(skb);
/* Be careful here. If this function gets more complicated and
* gcc suffers from register pressure on the x86, sk (in %ebx)
* might be destroyed here. This current version compiles correctly,
* but you have been warned.
*/
return 0;
csum_err:
TCP_INC_STATS_BH(TCP_MIB_INERRS);
goto discard;
}
上面的代碼中因?yàn)槲覀冋业降膕ock已經(jīng)是處于監(jiān)聽(tīng)狀態(tài)的,所以只會(huì)執(zhí)行這段代碼
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
}
tcp_v4_hnd_req()函數(shù)我們下一節(jié)論述,這是個(gè)很重要的函數(shù),因?yàn)槲覀兪恰暗谝淮挝帐帧,所以這個(gè)函數(shù)會(huì)再次取得我們的sock結(jié)構(gòu),如果與原來(lái)的sock不同就進(jìn)入了tcp_child_process()函數(shù)
int tcp_child_process(struct sock *parent, struct sock *child,
struct sk_buff *skb)
{
int ret = 0;
int state = child->sk_state;
if (!sock_owned_by_user(child)) {
ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),
skb->len);
/* Wakeup parent, send SIGIO */
if (state == TCP_SYN_RECV && child->sk_state != state)
parent->sk_data_ready(parent, 0);
} else {
/* Alas, it is possible again, because we do lookup
* in main socket hash table and lock on listening
* socket does not protect us more.
*/
sk_add_backlog(child, skb);
}
bh_unlock_sock(child);
sock_put(child);
return ret;
}
注意sock_owned_by_user()是檢查是否正在釋放sock結(jié)構(gòu),當(dāng)然我們服務(wù)器端并沒(méi)有釋放操作,所以會(huì)執(zhí)行tcp_rcv_state_process()函數(shù),函數(shù)很長(zhǎng)但是我們只關(guān)心與我們追蹤的線索有關(guān)的過(guò)程
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
。。。。。。
case TCP_LISTEN:
if (th->ack)
return 1;
if (th->rst)
goto discard;
if (th->syn) {
if (icsk->icsk_af_ops->conn_request(sk, skb) 0)
return 1;
/* Now we have several options: In theory there is
* nothing else in the frame. KA9Q has an option to
* send data with the syn, BSD accepts data with the
* syn up to the [to be] advertised window and
* Solaris 2.1 gives you a protocol error. For now
* we just ignore it, that fits the spec precisely
* and avoids incompatibilities. It would be nice in
* future to drop through and process the data.
*
* Now that TTCP is starting to be used we ought to
* queue this data.
* But, this leaves one open to an easy denial of
* service attack, and SYN cookies can't defend
* against this problem. So, we drop the data
* in the interest of security over speed unless
* it's still in use.
*/
kfree_skb(skb);
return 0;
}
。。。。。。
}
我們知道客戶端傳遞過(guò)來(lái)的是SYN包,所以會(huì)執(zhí)行icsk->icsk_af_ops->conn_request(sk, skb),這里我們?cè)诜治龇⻊?wù)器端的socket創(chuàng)建文章時(shí)
http://blog.chinaunix.net/u2/64681/showart_1360583.html
在tcp_v4_init_sock()函數(shù)中看到
icsk->icsk_af_ops = &ipv4_specific;
也就是會(huì)執(zhí)行鉤子結(jié)構(gòu)ipv4_specific中的conn_request()
struct inet_connection_sock_af_ops ipv4_specific = {
.queue_xmit = ip_queue_xmit,
.send_check = tcp_v4_send_check,
.rebuild_header = inet_sk_rebuild_header,
.conn_request = tcp_v4_conn_request,
.syn_recv_sock = tcp_v4_syn_recv_sock,
.remember_stamp = tcp_v4_remember_stamp,
.net_header_len = sizeof(struct iphdr),
.setsockopt = ip_setsockopt,
.getsockopt = ip_getsockopt,
.addr2sockaddr = inet_csk_addr2sockaddr,
.sockaddr_len = sizeof(struct sockaddr_in),
.bind_conflict = inet_csk_bind_conflict,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_ip_setsockopt,
.compat_getsockopt = compat_ip_getsockopt,
#endif
};
很明顯進(jìn)入了tcp_v4_conn_request()函數(shù),時(shí)間關(guān)系,我們明天繼續(xù)追蹤這個(gè)函數(shù)
本文來(lái)自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):
http://blog.chinaunix.net/u2/64681/showart_1656780.html
歡迎光臨 Chinaunix (http://72891.cn/)
Powered by Discuz! X3.2