<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > linux內(nèi)核中的copy_to_user和copy_from_user(一)

          linux內(nèi)核中的copy_to_user和copy_from_user(一)

          作者: 時(shí)間:2016-11-22 來源:網(wǎng)絡(luò) 收藏
          Kernel version:2.6.14

          CPU architecture:ARM920T

          本文引用地址:http://www.ex-cimer.com/article/201611/319995.htm

          Author:ce123(http://blog.csdn.net/ce123)

          1.copy_from_user

          在學(xué)習(xí)Linux內(nèi)核驅(qū)動(dòng)的時(shí)候,經(jīng)常會(huì)碰到copy_from_user和copy_to_user這兩個(gè)函數(shù),設(shè)備驅(qū)動(dòng)程序中的ioctl函數(shù)就經(jīng)常會(huì)用到。這兩個(gè)函數(shù)負(fù)責(zé)在用戶空間和內(nèi)核空間傳遞數(shù)據(jù)。首先看看它們的定義(linux/include/asm-arm/uaccess.h),先看copy_from_user:

          [plain]view plaincopy
          print?
          1. staticinlineunsignedlongcopy_from_user(void*to,constvoid__user*from,unsignedlongn)
          2. {
          3. if(access_ok(VERIFY_READ,from,n))
          4. n=__arch_copy_from_user(to,from,n);
          5. else/*securityhole-plugit*/
          6. memzero(to,n);
          7. returnn;
          8. }

          先看函數(shù)的三個(gè)參數(shù):*to是內(nèi)核空間的指針,*from是用戶空間指針,n表示從用戶空間想內(nèi)核空間拷貝數(shù)據(jù)的字節(jié)數(shù)。如果成功執(zhí)行拷貝操作,則返回0,否則返回還沒有完成拷貝的字節(jié)數(shù)。

          這個(gè)函數(shù)從結(jié)構(gòu)上來分析,其實(shí)都可以分為兩個(gè)部分:
          1. 首先檢查用戶空間的地址指針是否有效;
          2. 調(diào)用__arch_copy_from_user函數(shù)。

          1.1.access_ok

          access_ok用來對(duì)用戶空間的地址指針from作某種有效性檢驗(yàn),這個(gè)宏和體系結(jié)構(gòu)相關(guān),在arm平臺(tái)上為(linux/include/asm-arm/uaccess.h):

          [plain]view plaincopy
          print?
          1. #define__range_ok(addr,size)({
          2. unsignedlongflag,sum;
          3. __chk_user_ptr(addr);
          4. __asm__("adds%1,%2,%3;sbcccs%1,%1,%0;movcc%0,#0"
          5. :"=&r"(flag),"=&r"(sum)
          6. :"r"(addr),"Ir"(size),"0"(current_thread_info()->addr_limit)
          7. :"cc");
          8. flag;})
          9. #defineaccess_ok(type,addr,size)(__range_ok(addr,size)==0)
          可以看到access_ok中第一個(gè)參數(shù)type并沒有用到,__range_ok的作用在于判斷addr+size之后是否還在進(jìn)程的用戶空間范圍之內(nèi)。下面我們具體看一下。這段代碼涉及到GCC內(nèi)聯(lián)匯編,不懂的朋友可以先看看這篇博客(http://blog.csdn.net/ce123/article/details/8209702)。
          (1)unsigned long flag, sum;\定義兩個(gè)變量
          • flag:保存結(jié)果的變量:非零代表地址無效,零代表地址可以訪問。初始存放非零值(current_thread_info()->addr_limit),也就是當(dāng)前進(jìn)程的地址上限值。
          • sum:保存要訪問的地址范圍末端,用于和當(dāng)前進(jìn)程地址空間限制數(shù)據(jù)做比較。
          (2)__chk_user_ptr(addr);\定義是一個(gè)空函數(shù)
          這個(gè)函數(shù)涉及到__CHECKER__宏的判斷,__CHECKER__宏在通過Sparse(Semantic Parser for C)工具對(duì)內(nèi)核代碼進(jìn)行檢查時(shí)會(huì)定義的。在使用make C=1或C=2時(shí)便會(huì)調(diào)用該工具,這個(gè)工具可以檢查在代碼中聲明了sparse所能檢查到的相關(guān)屬性的內(nèi)核函數(shù)和變量。
          • 如果定義了__CHECKER__,__chk_user_ptr和__chk_io_ptr在這里只聲明函數(shù),沒有函數(shù)體,目的就是在編譯過程中Sparse能夠捕捉到編譯錯(cuò)誤,檢查參數(shù)的類型。
          • 如果沒有定義__CHECKER__,這就是一個(gè)空函數(shù)。

          請(qǐng)看具體的定義(linux/compiler.h):

          [plain]view plaincopy
          print?
          1. #ifdef__CHECKER__
          2. ...
          3. externvoid__chk_user_ptr(void__user*);
          4. externvoid__chk_io_ptr(void__iomem*);
          5. #else
          6. ...
          7. #define__chk_user_ptr(x)(void)0
          8. #define__chk_io_ptr(x)(void)0
          9. ...
          10. #endif
          (3)接下來是匯編:
          adds %1, %2, %3
          sum = addr + size 這個(gè)操作影響狀態(tài)位(目的是影響是進(jìn)位標(biāo)志C),以下的兩個(gè)指令都帶有條件CC,也就是當(dāng)C=0的時(shí)候才執(zhí)行。

          如果上面的加法指令進(jìn)位了(C=1),則以下的指令都不執(zhí)行,flag就為初始值current_thread_info()->addr_limit(非0),并返回。
          如果沒有進(jìn)位(C=0),就執(zhí)行下面的指令:
          sbcccs %1, %1, %0
          sum = sum - flag - 1,也就是(addr + size) - (current_thread_info()->addr_limit) - 1,操作影響符號(hào)位。
          如果(addr + size) >= (current_thread_info()->addr_limit) - 1,則C=1
          如果(addr + size) < (current_thread_info()->addr_limit) - 1,則C=0
          當(dāng)C=0的時(shí)候執(zhí)行以下指令,否則跳過(flag非零)。
          movcc %0, #0
          flag = 0,給flag賦值0。

          綜上所述:__range_ok宏其實(shí)等價(jià)于:

          • 如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值
          • 如果(addr + size) < (current_thread_info()->addr_limit),返回零
          而access_ok就是檢驗(yàn)將要操作的用戶空間的地址范圍是否在當(dāng)前進(jìn)程的用戶地址空間限制中。這個(gè)宏的功能很簡(jiǎn)單,完全可以用C實(shí)現(xiàn),不是必須使用匯編。但于這兩個(gè)函數(shù)使用頻繁,就使用匯編來實(shí)現(xiàn)部分功能來增加效率。
          從這里再次可以認(rèn)識(shí)到,copy_from_user的使用是結(jié)合進(jìn)程上下文的,因?yàn)樗麄円L問“user”的內(nèi)存空間,這個(gè)“user”必須是某個(gè)特定的進(jìn)程。通過上面的源碼就知道,其中使用了current_thread_info()來檢查空間是否可以訪問。如果在驅(qū)動(dòng)中使用這兩個(gè)函數(shù),必須是在實(shí)現(xiàn)系統(tǒng)調(diào)用的函數(shù)中使用,不可在實(shí)現(xiàn)中斷處理的函數(shù)中使用。如果在中斷上下文中使用了,那代碼就很可能操作了根本不相關(guān)的進(jìn)程地址空間。其次由于操作的頁(yè)面可能被換出,這兩個(gè)函數(shù)可能會(huì)休眠,所以同樣不可在中斷上下文中使用。

          1.2.__arch_copy_from_user

          在深入講解之前,我們先想一個(gè)問題:為什么要使用copy_from_user函數(shù)???理論上,內(nèi)核空間可以直接使用用戶空間傳過來的指針,即使要做數(shù)據(jù)拷貝的動(dòng)作,也可以直接使用memcpy,事實(shí)上,在沒有MMU的體系架構(gòu)上,copy_form_user最終的實(shí)現(xiàn)就是利用了memcpy。但對(duì)于大多數(shù)有MMU的平臺(tái),情況就有了一些變化:用戶空間傳過來的指針是在虛擬地址空間上的,它指向的虛擬地址空間很可能還沒有真正映射到實(shí)際的物理頁(yè)面上。但這又能怎樣呢?缺頁(yè)導(dǎo)致的異常會(huì)透明的被內(nèi)核予以修復(fù)(為缺頁(yè)的地址空間提交新的物理頁(yè)面),訪問到缺頁(yè)的指令會(huì)繼續(xù)運(yùn)行仿佛什么都沒有發(fā)生一樣。但這只是用戶空間缺頁(yè)異常的行為,在內(nèi)核空間這樣卻因一場(chǎng)必須被顯示的修復(fù),這是由內(nèi)核提供的缺頁(yè)異常處理函數(shù)的設(shè)計(jì)模式?jīng)Q定的,其背后的思想后:在內(nèi)核態(tài)中,如果程序試圖訪問一個(gè)尚未提交物理頁(yè)面的用戶空間地址,內(nèi)核必須對(duì)此保持警惕而不能像用戶空間那樣毫無察覺。
          如果內(nèi)核訪問一個(gè)尚未被提交物理頁(yè)面的空間,將產(chǎn)生缺頁(yè)異常,內(nèi)核會(huì)調(diào)用do_page_fault,因?yàn)楫惓0l(fā)生在內(nèi)核空間,do_page_fault將調(diào)用search_exception_tables在“ __ex_table”中查找異常指令的修復(fù)指令,在__arch_copy_from_user函數(shù)中經(jīng)常使用USER宏,這個(gè)宏中了定義了“__ex_table”section。
          linux/include/asm-arm/assembler.h
          [plain]view plaincopy
          print?
          1. #defineUSER(x...)
          2. 9999:x;
          3. .section__ex_table,"a";
          4. .align3;
          5. .long9999b,9001f;
          6. .previous
          該定義中有如下數(shù)據(jù);
          [plain]view plaincopy
          print?
          1. .long9999b,9001f;
          其中9999b對(duì)應(yīng)標(biāo)號(hào)9999處的指令,9001f是9001處的指令,是9999b處指令的修復(fù)指令。這樣,當(dāng)標(biāo)號(hào)9999處發(fā)生缺頁(yè)異常時(shí),系統(tǒng)將調(diào)用do_page_fault提交物理頁(yè)面,然后跳到9001繼續(xù)執(zhí)行。
          如果在驅(qū)動(dòng)程序中不使用copy_from_user而用memcpy來代替,對(duì)于上述的情形會(huì)產(chǎn)生什么結(jié)果呢?當(dāng)標(biāo)號(hào)9999出發(fā)生缺頁(yè)異常時(shí),系統(tǒng)在“__ex_table”section總將找不到修復(fù)地址,因?yàn)閙emcpy沒有像copy_from_user那樣定義一個(gè)“__ex_table”section,此時(shí)do_page_fault將通過no_context函數(shù)產(chǎn)生Oops。極有可能會(huì)看到類似如下信息:
          Unable to handle kernel NULL pointer dereference at virtual address 00000fe0
          所有為了確保設(shè)備驅(qū)動(dòng)程序的安全,應(yīng)該使用copy_from_user函數(shù)而不是memcpy。
          下面我們深入分析__arch_copy_from_user函數(shù)的實(shí)現(xiàn),該函數(shù)是用匯編實(shí)現(xiàn)的,定義在linux/arch/arm/lib/uaccess.S文件中。

          [plain]view plaincopy
          print?
          1. /*Prototype:unsignedlong__arch_copy_from_user(void*to,constvoid*from,unsignedlongn);
          2. *Purpose:copyablockfromusermemorytokernelmemory
          3. *Params:to-kernelmemory
          4. *:from-usermemory
          5. *:n-numberofbytestocopy
          6. *Returns:NumberofbytesNOTcopied.
          7. */
          8. .cfu_dest_not_aligned:
          9. rsbip,ip,#4
          10. cmpip,#2
          11. USER(ldrbtr3,[r1],#1)@Mayfault
          12. strbr3,[r0],#1
          13. USER(ldrgebtr3,[r1],#1)@Mayfault
          14. strgebr3,[r0],#1
          15. USER(ldrgtbtr3,[r1],#1)@Mayfault
          16. strgtbr3,[r0],#1
          17. subr2,r2,ip
          18. b.cfu_dest_aligned
          19. ENTRY(__arch_copy_from_user)
          20. stmfdsp!,{r0,r2,r4-r7,lr}
          21. cmpr2,#4
          22. blt.cfu_not_enough
          23. PLD(pld[r1,#0])
          24. PLD(pld[r0,#0])
          25. andsip,r0,#3
          26. bne.cfu_dest_not_aligned
          27. .cfu_dest_aligned:
          28. andsip,r1,#3
          29. bne.cfu_src_not_aligned
          30. /*
          31. *Seeingastherehastobeatleast8bytestocopy,wecan
          32. *copyoneword,andforceauser-modepagefault...
          33. */
          34. .cfu_0fupi:subsr2,r2,#4
          35. addmiip,r2,#4
          36. bmi.cfu_0nowords
          37. USER(ldrtr3,[r1],#4)
          38. strr3,[r0],#4
          39. movip,r1,lsl#32-PAGE_SHIFT@Oneachpage,useald/st??tinstruction
          40. rsbip,ip,#0
          41. movsip,ip,lsr#32-PAGE_SHIFT
          42. beq.cfu_0fupi
          43. /*
          44. *ip=maxno.ofbytestocopybeforeneedinganother"strt"insn
          45. */
          46. cmpr2,ip
          47. movltip,r2
          48. subr2,r2,ip
          49. subsip,ip,#32
          50. blt.cfu_0rem8lp
          51. PLD(pld[r1,#28])
          52. PLD(pld[r0,#28])
          53. PLD(subsip,ip,#64)
          54. PLD(blt.cfu_0cpynopld)
          55. PLD(pld[r1,#60])
          56. PLD(pld[r0,#60])
          57. .cfu_0cpy8lp:
          58. PLD(pld[r1,#92])
          59. PLD(pld[r0,#92])
          60. .cfu_0cpynopld:ldmiar1!,{r3-r6}@Shouldntfault
          61. stmiar0!,{r3-r6}
          62. ldmiar1!,{r3-r6}@Shouldntfault
          63. subsip,ip,#32
          64. stmiar0!,{r3-r6}
          65. bpl.cfu_0cpy8lp
          66. PLD(cmnip,#64)
          67. PLD(bge.cfu_0cpynopld)
          68. PLD(addip,ip,#64)
          69. .cfu_0rem8lp:cmnip,#16
          70. ldmgeiar1!,{r3-r6}@Shouldntfault
          71. stmgeiar0!,{r3-r6}
          72. tstip,#8
          73. ldmneiar1!,{r3-r4}@Shouldntfault
          74. stmneiar0!,{r3-r4}
          75. tstip,#4
          76. ldrnetr3,[r1],#4@Shouldntfault
          77. strner3,[r0],#4
          78. andsip,ip,#3
          79. beq.cfu_0fupi
          80. .cfu_0nowords:teqip,#0
          81. beq.cfu_finished
          82. .cfu_nowords:cmpip,#2
          83. USER(ldrbtr3,[r1],#1)@Mayfault
          84. strbr3,[r0],#1
          85. USER(ldrgebtr3,[r1],#1)@Mayfault
          86. strgebr3,[r0],#1
          87. USER(ldrgtbtr3,[r1],#1)@Mayfault
          88. strgtbr3,[r0],#1
          89. b.cfu_finished
          90. .cfu_not_enough:
          91. movsip,r2
          92. bne.cfu_nowords
          93. .cfu_finished:movr0,#0
          94. addsp,sp,#8
          95. LOADREGS(fd,sp!,{r4-r7,pc})
          96. .cfu_src_not_aligned:
          97. bicr1,r1,#3
          98. USER(ldrtr7,[r1],#4)@Mayfault
          99. cmpip,#2
          100. bgt.cfu_3fupi
          101. beq.cfu_2fupi
          102. .cfu_1fupi:subsr2,r2,#4
          103. addmiip,r2,#4
          104. bmi.cfu_1nowords
          105. movr3,r7,pull#8
          106. USER(ldrtr7,[r1],#4)@Mayfault
          107. orrr3,r3,r7,push#24
          108. strr3,[r0],#4
          109. movip,r1,lsl#32-PAGE_SHIFT
          110. rsbip,ip,#0
          111. movsip,ip,lsr#32-PAGE_SHIFT
          112. beq.cfu_1fupi
          113. cmpr2,ip
          114. movltip,r2
          115. subr2,r2,ip
          116. subsip,ip,#16
          117. blt.cfu_1rem8lp
          118. PLD(pld[r1,#12])
          119. PLD(pld[r0,#12])
          120. PLD(subsip,ip,#32)
          121. PLD(blt.cfu_1cpynopld)
          122. PLD(pld[r1,#28])
          123. PLD(pld[r0,#28])
          124. .cfu_1cpy8lp:
          125. PLD(pld[r1,#44])
          126. PLD(pld[r0,#44])
          127. .cfu_1cpynopld:movr3,r7,pull#8
          128. ldmiar1!,{r4-r7}@Shouldntfault
          129. subsip,ip,#16
          130. orrr3,r3,r4,push#24
          131. movr4,r4,pull#8
          132. orrr4,r4,r5,push#24
          133. movr5,r5,pull#8
          134. orrr5,r5,r6,push#24
          135. movr6,r6,pull#8
          136. orrr6,r6,r7,push#24
          137. stmiar0!,{r3-r6}
          138. bpl.cfu_1cpy8lp
          139. PLD(cmnip,#32)
          140. PLD(bge.cfu_1cpynopld)
          141. PLD(addip,ip,#32)
          142. .cfu_1rem8lp:tstip,#8
          143. movner3,r7,pull#8
          144. ldmneiar1!,{r4,r7}@Shouldntfault
          145. orrner3,r3,r4,push#24
          146. movner4,r4,pull#8
          147. orrner4,r4,r7,push#24
          148. stmneiar0!,{r3-r4}
          149. tstip,#4
          150. movner3,r7,pull#8
          151. USER(ldrnetr7,[r1],#4)@Mayfault
          152. orrner3,r3,r7,push#24
          153. strner3,[r0],#4
          154. andsip,ip,#3
          155. beq.cfu_1fupi
          156. .cfu_1nowords:movr3,r7,get_byte_1
          157. teqip,#0
          158. beq.cfu_finished
          159. cmpip,#2
          160. strbr3,[r0],#1
          161. movger3,r7,get_byte_2
          162. strgebr3,[r0],#1
          163. movgtr3,r7,get_byte_3
          164. strgtbr3,[r0],#1
          165. b.cfu_finished
          166. .cfu_2fupi:subsr2,r2,#4
          167. addmiip,r2,#4
          168. bmi.cfu_2nowords
          169. movr3,r7,pull#16
          170. USER(ldrtr7,[r1],#4)@Mayfault
          171. orrr3,r3,r7,push#16
          172. strr3,[r0],#4
          173. movip,r1,lsl#32-PAGE_SHIFT
          174. rsbip,ip,#0
          175. movsip,ip,lsr#32-PAGE_SHIFT
          176. beq.cfu_2fupi
          177. cmpr2,ip
          178. movltip,r2
          179. subr2,r2,ip
          180. subsip,ip,#16
          181. blt.cfu_2rem8lp
          182. PLD(pld[r1,#12])
          183. PLD(pld[r0,#12])
          184. PLD(subsip,ip,#32)
          185. PLD(blt.cfu_2cpynopld)
          186. PLD(pld[r1,#28])
          187. PLD(pld[r0,#28])
          188. .cfu_2cpy8lp:
          189. PLD(pld[r1,#44])
          190. PLD(pld[r0,#44])
          191. .cfu_2cpynopld:movr3,r7,pull#16
          192. ldmiar1!,{r4-r7}@Shouldntfault
          193. subsip,ip,#16
          194. orrr3,r3,r4,push#16
          195. movr4,r4,pull#16
          196. orrr4,r4,r5,push#16
          197. movr5,r5,pull#16
          198. orrr5,r5,r6,push#16
          199. movr6,r6,pull#16
          200. orrr6,r6,r7,push#16
          201. stmiar0!,{r3-r6}
          202. bpl.cfu_2cpy8lp
          203. PLD(cmnip,#32)
          204. PLD(bge.cfu_2cpynopld)
          205. PLD(addip,ip,#32)
          206. .cfu_2rem8lp:tstip,#8
          207. movner3,r7,pull#16
          208. ldmneiar1!,{r4,r7}@Shouldntfault
          209. orrner3,r3,r4,push#16
          210. movner4,r4,pull#16
          211. orrner4,r4,r7,push#16
          212. stmneiar0!,{r3-r4}
          213. tstip,#4
          214. movner3,r7,pull#16
          215. USER(ldrnetr7,[r1],#4)@Mayfault
          216. orrner3,r3,r7,push#16
          217. strner3,[r0],#4
          218. andsip,ip,#3
          219. beq.cfu_2fupi
          220. .cfu_2nowords:movr3,r7,get_byte_2
          221. teqip,#0
          222. beq.cfu_finished
          223. cmpip,#2
          224. strbr3,[r0],#1
          225. movger3,r7,get_byte_3
          226. strgebr3,[r0],#1
          227. USER(ldrgtbtr3,[r1],#0)@Mayfault
          228. strgtbr3,[r0],#1
          229. b.cfu_finished
          230. .cfu_3fupi:subsr2,r2,#4
          231. addmiip,r2,#4
          232. bmi.cfu_3nowords
          233. movr3,r7,pull#24
          234. USER(ldrtr7,[r1],#4)@Mayfault
          235. orrr3,r3,r7,push#8
          236. strr3,[r0],#4
          237. movip,r1,lsl#32-PAGE_SHIFT
          238. rsbip,ip,#0
          239. movsip,ip,lsr#32-PAGE_SHIFT
          240. beq.cfu_3fupi
          241. cmpr2,ip
          242. movltip,r2
          243. subr2,r2,ip
          244. subsip,ip,#16
          245. blt.cfu_3rem8lp
          246. PLD(pld[r1,#12])
          247. PLD(pld[r0,#12])
          248. PLD(subsip,ip,#32)
          249. PLD(blt.cfu_3cpynopld)
          250. PLD(pld[r1,#28])
          251. PLD(pld[r0,#28])
          252. .cfu_3cpy8lp:
          253. PLD(pld[r1,#44])
          254. PLD(pld[r0,#44])
          255. .cfu_3cpynopld:movr3,r7,pull#24
          256. ldmiar1!,{r4-r7}@Shouldntfault
          257. orrr3,r3,r4,push#8
          258. movr4,r4,pull#24
          259. orrr4,r4,r5,push#8
          260. movr5,r5,pull#24
          261. orrr5,r5,r6,push#8
          262. movr6,r6,pull#24
          263. orrr6,r6,r7,push#8
          264. stmiar0!,{r3-r6}
          265. subsip,ip,#16
          266. bpl.cfu_3cpy8lp
          267. PLD(cmnip,#32)
          268. PLD(bge.cfu_3cpynopld)
          269. PLD(addip,ip,#32)
          270. .cfu_3rem8lp:tstip,#8
          271. movner3,r7,pull#24
          272. ldmneiar1!,{r4,r7}@Shouldntfault
          273. orrner3,r3,r4,push#8
          274. movner4,r4,pull#24
          275. orrner4,r4,r7,push#8
          276. stmneiar0!,{r3-r4}
          277. tstip,#4
          278. movner3,r7,pull#24
          279. USER(ldrnetr7,[r1],#4)@Mayfault
          280. orrner3,r3,r7,push#8
          281. strner3,[r0],#4
          282. andsip,ip,#3
          283. beq.cfu_3fupi
          284. .cfu_3nowords:movr3,r7,get_byte_3
          285. teqip,#0
          286. beq.cfu_finished
          287. cmpip,#2
          288. strbr3,[r0],#1
          289. USER(ldrgebtr3,[r1],#1)@Mayfault
          290. strgebr3,[r0],#1
          291. USER(ldrgtbtr3,[r1],#1)@Mayfault
          292. strgtbr3,[r0],#1
          293. b.cfu_finished
          294. .section.fixup,"ax"
          295. .align0
          296. /*
          297. *Wetookanexception.r0containsapointerto
          298. *thebytenotcopied.
          299. */
          300. 9001:ldrr2,[sp],#4@void*to
          301. subr2,r0,r2@bytescopied
          302. ldrr1,[sp],#4@unsignedlongcount
          303. subsr4,r1,r2@byteslefttocopy
          304. movner1,r4
          305. blne__memzero
          306. movr0,r4
          307. LOADREGS(fd,sp!,{r4-r7,pc})
          308. .previous
          我們將在另一篇博文中詳細(xì)分析該函數(shù)。


          評(píng)論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();