linux内核中的copy_to_user和copy_from_user(一)

2017年12月21日 20:07:32 prike 阅读数:4768

linux内核中的copy_to_user和copy_from_user(一)

Kernel version:2.6.14

CPU architecture:ARM920T

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

1.copy_from_user

在学习Linux内核驱动的时候,经常会碰到copy_from_user和copy_to_user这两个函数,设备驱动程序中的ioctl函数就经常会用到。这两个函数负责在用户空间和内核空间传递数据。首先看看它们的定义(linux/include/asm-arm/uaccess.h),先看copy_from_user:

[plain] view plain copy

  1. static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
  2. {
  3. if (access_ok(VERIFY_READ, from, n))
  4. n = __arch_copy_from_user(to, from, n);
  5. else /* security hole - plug it */
  6. memzero(to, n);
  7. return n;
  8. }

先看函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。

这个函数从结构上来分析,其实都可以分为两个部分:

  1. 首先检查用户空间的地址指针是否有效;
  2. 调用__arch_copy_from_user函数。

1.1.access_ok

access_ok用来对用户空间的地址指针from作某种有效性检验,这个宏和体系结构相关,在arm平台上为(linux/include/asm-arm/uaccess.h):

[plain] view plain copy

  1. #define __range_ok(addr,size) ({ \
  2. unsigned long flag, 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. #define access_ok(type,addr,size)   (__range_ok(addr,size) == 0)

可以看到access_ok中第一个参数type并没有用到,__range_ok的作用在于判断addr+size之后是否还在进程的用户空间范围之内。下面我们具体看一下。这段代码涉及到GCC内联汇编,不懂的朋友可以先看看这篇博客(http://blog.csdn.net/ce123/article/details/8209702)。
(1)unsigned long flag, sum;\\定义两个变量

  • flag:保存结果的变量:非零代表地址无效,零代表地址可以访问。初始存放非零值(current_thread_info()->addr_limit),也就是当前进程的地址上限值。
  • sum:保存要访问的地址范围末端,用于和当前进程地址空间限制数据做比较。

(2)__chk_user_ptr(addr);\\定义是一个空函数
这个函数涉及到__CHECKER__宏的判断,__CHECKER__宏在通过Sparse(Semantic Parser for C)工具对内核代码进行检查时会定义的。在使用make C=1或C=2时便会调用该工具,这个工具可以检查在代码中声明了sparse所能检查到的相关属性的内核函数和变量。

  • 如果定义了__CHECKER__,__chk_user_ptr和__chk_io_ptr在这里只声明函数,没有函数体,目的就是在编译过程中Sparse能够捕捉到编译错误,检查参数的类型。
  • 如果没有定义__CHECKER__,这就是一个空函数。

请看具体的定义(linux/compiler.h):

[plain] view plain copy

  1. #ifdef __CHECKER__
  2. ...
  3. extern void __chk_user_ptr(void __user *);
  4. extern void __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 这个操作影响状态位(目的是影响是进位标志C),以下的两个指令都带有条件CC,也就是当C=0的时候才执行。

如果上面的加法指令进位了(C=1),则以下的指令都不执行,flag就为初始值current_thread_info()->addr_limit(非0),并返回。
如果没有进位(C=0),就执行下面的指令:
sbcccs %1, %1, %0 
sum = sum - flag - 1,也就是(addr + size) -  (current_thread_info()->addr_limit) - 1,操作影响符号位。
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,则C=1
如果(addr + size) < (current_thread_info()->addr_limit) - 1,则C=0
当C=0的时候执行以下指令,否则跳过(flag非零)。
movcc %0, #0
flag = 0,给flag赋值0。

综上所述:__range_ok宏其实等价于:

  • 如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值
  • 如果(addr + size) < (current_thread_info()->addr_limit),返回零

而access_ok就是检验将要操作的用户空间的地址范围是否在当前进程的用户地址空间限制中。这个宏的功能很简单,完全可以用C实现,不是必须使用汇编。但于这两个函数使用频繁,就使用汇编来实现部分功能来增加效率。
从这里再次可以认识到,copy_from_user的使用是结合进程上下文的,因为他们要访问“user”的内存空间,这个“user”必须是某个特定的进程。通过上面的源码就知道,其中使用了current_thread_info()来检查空间是否可以访问。如果在驱动中使用这两个函数,必须是在实现系统调用的函数中使用,不可在实现中断处理的函数中使用。如果在中断上下文中使用了,那代码就很可能操作了根本不相关的进程地址空间。其次由于操作的页面可能被换出,这两个函数可能会休眠,所以同样不可在中断上下文中使用。

1.2.__arch_copy_from_user

在深入讲解之前,我们先想一个问题:为什么要使用copy_from_user函数???理论上,内核空间可以直接使用用户空间传过来的指针,即使要做数据拷贝的动作,也可以直接使用memcpy,事实上,在没有MMU的体系架构上,copy_form_user最终的实现就是利用了memcpy。但对于大多数有MMU的平台,情况就有了一些变化:用户空间传过来的指针是在虚拟地址空间上的,它指向的虚拟地址空间很可能还没有真正映射到实际的物理页面上。但这又能怎样呢?缺页导致的异常会透明的被内核予以修复(为缺页的地址空间提交新的物理页面),访问到缺页的指令会继续运行仿佛什么都没有发生一样。但这只是用户空间缺页异常的行为,在内核空间这样却因一场必须被显示的修复,这是由内核提供的缺页异常处理函数的设计模式决定的,其背后的思想后:在内核态中,如果程序试图访问一个尚未提交物理页面的用户空间地址,内核必须对此保持警惕而不能像用户空间那样毫无察觉。

如果内核访问一个尚未被提交物理页面的空间,将产生缺页异常,内核会调用do_page_fault,因为异常发生在内核空间,do_page_fault将调用search_exception_tables在“ __ex_table”中查找异常指令的修复指令,在__arch_copy_from_user函数中经常使用USER宏,这个宏中了定义了“__ex_table”section。

linux/include/asm-arm/assembler.h

[plain] view plain copy

  1. #define USER(x...)              \
  2. 9999:   x;                  \
  3. .section __ex_table,"a";        \
  4. .align  3;              \
  5. .long   9999b,9001f;            \
  6. .previous

该定义中有如下数据;

[plain] view plain copy

  1. .long   9999b,9001f;

其中9999b对应标号9999处的指令,9001f是9001处的指令,是9999b处指令的修复指令。这样,当标号9999处发生缺页异常时,系统将调用do_page_fault提交物理页面,然后跳到9001继续执行。

如果在驱动程序中不使用copy_from_user而用memcpy来代替,对于上述的情形会产生什么结果呢?当标号9999出发生缺页异常时,系统在“__ex_table”section总将找不到修复地址,因为memcpy没有像copy_from_user那样定义一个“__ex_table”section,此时do_page_fault将通过no_context函数产生Oops。极有可能会看到类似如下信息:

Unable to handle kernel NULL pointer dereference at virtual address 00000fe0

所有为了确保设备驱动程序的安全,应该使用copy_from_user函数而不是memcpy。

下面我们深入分析__arch_copy_from_user函数的实现,该函数是用汇编实现的,定义在linux/arch/arm/lib/uaccess.S文件中。

[plain] view plain copy

  1. /* Prototype: unsigned long __arch_copy_from_user(void *to,const void *from,unsigned long n);
  2. * Purpose  : copy a block from user memory to kernel memory
  3. * Params   : to   - kernel memory
  4. *          : from - user memory
  5. *          : n    - number of bytes to copy
  6. * Returns  : Number of bytes NOT copied.
  7. */
  8. .cfu_dest_not_aligned:
  9. rsb ip, ip, #4
  10. cmp ip, #2
  11. USER(       ldrbt   r3, [r1], #1)           @ May fault
  12. strb    r3, [r0], #1
  13. USER(       ldrgebt r3, [r1], #1)           @ May fault
  14. strgeb  r3, [r0], #1
  15. USER(       ldrgtbt r3, [r1], #1)           @ May fault
  16. strgtb  r3, [r0], #1
  17. sub r2, r2, ip
  18. b   .cfu_dest_aligned
  19. ENTRY(__arch_copy_from_user)
  20. stmfd   sp!, {r0, r2, r4 - r7, lr}
  21. cmp r2, #4
  22. blt .cfu_not_enough
  23. PLD(    pld [r1, #0]        )
  24. PLD(    pld [r0, #0]        )
  25. ands    ip, r0, #3
  26. bne .cfu_dest_not_aligned
  27. .cfu_dest_aligned:
  28. ands    ip, r1, #3
  29. bne .cfu_src_not_aligned
  30. /*
  31. * Seeing as there has to be at least 8 bytes to copy, we can
  32. * copy one word, and force a user-mode page fault...
  33. */
  34. .cfu_0fupi: subs    r2, r2, #4
  35. addmi   ip, r2, #4
  36. bmi .cfu_0nowords
  37. USER(       ldrt    r3, [r1], #4)
  38. str r3, [r0], #4
  39. mov ip, r1, lsl #32 - PAGE_SHIFT    @ On each page, use a ld/st??t instruction
  40. rsb ip, ip, #0
  41. movs    ip, ip, lsr #32 - PAGE_SHIFT
  42. beq .cfu_0fupi
  43. /*
  44. * ip = max no. of bytes to copy before needing another "strt" insn
  45. */
  46. cmp r2, ip
  47. movlt   ip, r2
  48. sub r2, r2, ip
  49. subs    ip, ip, #32
  50. blt .cfu_0rem8lp
  51. PLD(    pld [r1, #28]       )
  52. PLD(    pld [r0, #28]       )
  53. PLD(    subs    ip, 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: ldmia   r1!, {r3 - r6}          @ Shouldnt fault
  61. stmia   r0!, {r3 - r6}
  62. ldmia   r1!, {r3 - r6}          @ Shouldnt fault
  63. subs    ip, ip, #32
  64. stmia   r0!, {r3 - r6}
  65. bpl .cfu_0cpy8lp
  66. PLD(    cmn ip, #64         )
  67. PLD(    bge .cfu_0cpynopld      )
  68. PLD(    add ip, ip, #64     )
  69. .cfu_0rem8lp:   cmn ip, #16
  70. ldmgeia r1!, {r3 - r6}          @ Shouldnt fault
  71. stmgeia r0!, {r3 - r6}
  72. tst ip, #8
  73. ldmneia r1!, {r3 - r4}          @ Shouldnt fault
  74. stmneia r0!, {r3 - r4}
  75. tst ip, #4
  76. ldrnet  r3, [r1], #4            @ Shouldnt fault
  77. strne   r3, [r0], #4
  78. ands    ip, ip, #3
  79. beq .cfu_0fupi
  80. .cfu_0nowords:  teq ip, #0
  81. beq .cfu_finished
  82. .cfu_nowords:   cmp ip, #2
  83. USER(       ldrbt   r3, [r1], #1)           @ May fault
  84. strb    r3, [r0], #1
  85. USER(       ldrgebt r3, [r1], #1)           @ May fault
  86. strgeb  r3, [r0], #1
  87. USER(       ldrgtbt r3, [r1], #1)           @ May fault
  88. strgtb  r3, [r0], #1
  89. b   .cfu_finished
  90. .cfu_not_enough:
  91. movs    ip, r2
  92. bne .cfu_nowords
  93. .cfu_finished:  mov r0, #0
  94. add sp, sp, #8
  95. LOADREGS(fd,sp!,{r4 - r7, pc})
  96. .cfu_src_not_aligned:
  97. bic r1, r1, #3
  98. USER(       ldrt    r7, [r1], #4)           @ May fault
  99. cmp ip, #2
  100. bgt .cfu_3fupi
  101. beq .cfu_2fupi
  102. .cfu_1fupi: subs    r2, r2, #4
  103. addmi   ip, r2, #4
  104. bmi .cfu_1nowords
  105. mov r3, r7, pull #8
  106. USER(       ldrt    r7, [r1], #4)           @ May fault
  107. orr r3, r3, r7, push #24
  108. str r3, [r0], #4
  109. mov ip, r1, lsl #32 - PAGE_SHIFT
  110. rsb ip, ip, #0
  111. movs    ip, ip, lsr #32 - PAGE_SHIFT
  112. beq .cfu_1fupi
  113. cmp r2, ip
  114. movlt   ip, r2
  115. sub r2, r2, ip
  116. subs    ip, ip, #16
  117. blt .cfu_1rem8lp
  118. PLD(    pld [r1, #12]       )
  119. PLD(    pld [r0, #12]       )
  120. PLD(    subs    ip, 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: mov r3, r7, pull #8
  128. ldmia   r1!, {r4 - r7}          @ Shouldnt fault
  129. subs    ip, ip, #16
  130. orr r3, r3, r4, push #24
  131. mov r4, r4, pull #8
  132. orr r4, r4, r5, push #24
  133. mov r5, r5, pull #8
  134. orr r5, r5, r6, push #24
  135. mov r6, r6, pull #8
  136. orr r6, r6, r7, push #24
  137. stmia   r0!, {r3 - r6}
  138. bpl .cfu_1cpy8lp
  139. PLD(    cmn ip, #32         )
  140. PLD(    bge .cfu_1cpynopld      )
  141. PLD(    add ip, ip, #32     )
  142. .cfu_1rem8lp:   tst ip, #8
  143. movne   r3, r7, pull #8
  144. ldmneia r1!, {r4, r7}           @ Shouldnt fault
  145. orrne   r3, r3, r4, push #24
  146. movne   r4, r4, pull #8
  147. orrne   r4, r4, r7, push #24
  148. stmneia r0!, {r3 - r4}
  149. tst ip, #4
  150. movne   r3, r7, pull #8
  151. USER(       ldrnet  r7, [r1], #4)           @ May fault
  152. orrne   r3, r3, r7, push #24
  153. strne   r3, [r0], #4
  154. ands    ip, ip, #3
  155. beq .cfu_1fupi
  156. .cfu_1nowords:  mov r3, r7, get_byte_1
  157. teq ip, #0
  158. beq .cfu_finished
  159. cmp ip, #2
  160. strb    r3, [r0], #1
  161. movge   r3, r7, get_byte_2
  162. strgeb  r3, [r0], #1
  163. movgt   r3, r7, get_byte_3
  164. strgtb  r3, [r0], #1
  165. b   .cfu_finished
  166. .cfu_2fupi: subs    r2, r2, #4
  167. addmi   ip, r2, #4
  168. bmi .cfu_2nowords
  169. mov r3, r7, pull #16
  170. USER(       ldrt    r7, [r1], #4)           @ May fault
  171. orr r3, r3, r7, push #16
  172. str r3, [r0], #4
  173. mov ip, r1, lsl #32 - PAGE_SHIFT
  174. rsb ip, ip, #0
  175. movs    ip, ip, lsr #32 - PAGE_SHIFT
  176. beq .cfu_2fupi
  177. cmp r2, ip
  178. movlt   ip, r2
  179. sub r2, r2, ip
  180. subs    ip, ip, #16
  181. blt .cfu_2rem8lp
  182. PLD(    pld [r1, #12]       )
  183. PLD(    pld [r0, #12]       )
  184. PLD(    subs    ip, 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: mov r3, r7, pull #16
  192. ldmia   r1!, {r4 - r7}          @ Shouldnt fault
  193. subs    ip, ip, #16
  194. orr r3, r3, r4, push #16
  195. mov r4, r4, pull #16
  196. orr r4, r4, r5, push #16
  197. mov r5, r5, pull #16
  198. orr r5, r5, r6, push #16
  199. mov r6, r6, pull #16
  200. orr r6, r6, r7, push #16
  201. stmia   r0!, {r3 - r6}
  202. bpl .cfu_2cpy8lp
  203. PLD(    cmn ip, #32         )
  204. PLD(    bge .cfu_2cpynopld      )
  205. PLD(    add ip, ip, #32     )
  206. .cfu_2rem8lp:   tst ip, #8
  207. movne   r3, r7, pull #16
  208. ldmneia r1!, {r4, r7}           @ Shouldnt fault
  209. orrne   r3, r3, r4, push #16
  210. movne   r4, r4, pull #16
  211. orrne   r4, r4, r7, push #16
  212. stmneia r0!, {r3 - r4}
  213. tst ip, #4
  214. movne   r3, r7, pull #16
  215. USER(       ldrnet  r7, [r1], #4)           @ May fault
  216. orrne   r3, r3, r7, push #16
  217. strne   r3, [r0], #4
  218. ands    ip, ip, #3
  219. beq .cfu_2fupi
  220. .cfu_2nowords:  mov r3, r7, get_byte_2
  221. teq ip, #0
  222. beq .cfu_finished
  223. cmp ip, #2
  224. strb    r3, [r0], #1
  225. movge   r3, r7, get_byte_3
  226. strgeb  r3, [r0], #1
  227. USER(       ldrgtbt r3, [r1], #0)           @ May fault
  228. strgtb  r3, [r0], #1
  229. b   .cfu_finished
  230. .cfu_3fupi: subs    r2, r2, #4
  231. addmi   ip, r2, #4
  232. bmi .cfu_3nowords
  233. mov r3, r7, pull #24
  234. USER(       ldrt    r7, [r1], #4)           @ May fault
  235. orr r3, r3, r7, push #8
  236. str r3, [r0], #4
  237. mov ip, r1, lsl #32 - PAGE_SHIFT
  238. rsb ip, ip, #0
  239. movs    ip, ip, lsr #32 - PAGE_SHIFT
  240. beq .cfu_3fupi
  241. cmp r2, ip
  242. movlt   ip, r2
  243. sub r2, r2, ip
  244. subs    ip, ip, #16
  245. blt .cfu_3rem8lp
  246. PLD(    pld [r1, #12]       )
  247. PLD(    pld [r0, #12]       )
  248. PLD(    subs    ip, 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: mov r3, r7, pull #24
  256. ldmia   r1!, {r4 - r7}          @ Shouldnt fault
  257. orr r3, r3, r4, push #8
  258. mov r4, r4, pull #24
  259. orr r4, r4, r5, push #8
  260. mov r5, r5, pull #24
  261. orr r5, r5, r6, push #8
  262. mov r6, r6, pull #24
  263. orr r6, r6, r7, push #8
  264. stmia   r0!, {r3 - r6}
  265. subs    ip, ip, #16
  266. bpl .cfu_3cpy8lp
  267. PLD(    cmn ip, #32         )
  268. PLD(    bge .cfu_3cpynopld      )
  269. PLD(    add ip, ip, #32     )
  270. .cfu_3rem8lp:   tst ip, #8
  271. movne   r3, r7, pull #24
  272. ldmneia r1!, {r4, r7}           @ Shouldnt fault
  273. orrne   r3, r3, r4, push #8
  274. movne   r4, r4, pull #24
  275. orrne   r4, r4, r7, push #8
  276. stmneia r0!, {r3 - r4}
  277. tst ip, #4
  278. movne   r3, r7, pull #24
  279. USER(       ldrnet  r7, [r1], #4)           @ May fault
  280. orrne   r3, r3, r7, push #8
  281. strne   r3, [r0], #4
  282. ands    ip, ip, #3
  283. beq .cfu_3fupi
  284. .cfu_3nowords:  mov r3, r7, get_byte_3
  285. teq ip, #0
  286. beq .cfu_finished
  287. cmp ip, #2
  288. strb    r3, [r0], #1
  289. USER(       ldrgebt r3, [r1], #1)           @ May fault
  290. strgeb  r3, [r0], #1
  291. USER(       ldrgtbt r3, [r1], #1)           @ May fault
  292. strgtb  r3, [r0], #1
  293. b   .cfu_finished
  294. .section .fixup,"ax"
  295. .align  0
  296. /*
  297. * We took an exception.  r0 contains a pointer to
  298. * the byte not copied.
  299. */
  300. 9001:       ldr r2, [sp], #4            @ void *to
  301. sub r2, r0, r2          @ bytes copied
  302. ldr r1, [sp], #4            @ unsigned long count
  303. subs    r4, r1, r2          @ bytes left to copy
  304. movne   r1, r4
  305. blne    __memzero
  306. mov r0, r4
  307. LOADREGS(fd,sp!, {r4 - r7, pc})
  308. .previous

我们将在另一篇博文中详细分析该函数。

forward from:

https://blog.csdn.net/prike/article/details/78867270

linux内核中的copy_to_user和copy_from_user(一)相关推荐

  1. 论文中文翻译——Double-Fetch情况如何演变为Double-Fetch漏洞:Linux内核中的双重获取研究

    本论文相关内容 论文下载地址--Web Of Science 论文中文翻译--How Double-Fetch Situations turn into Double-Fetch Vulnerabil ...

  2. Linux内核中使用内存检测

    目录 一.slub内存检测 1.越界访问 2.释放后再访问 3.无效的释放 4.实验输出 二.KASAN 内存检测 1.数组越界 2.栈的越界访问 3.实验输出 一般的内存访问错误如下: 越界访问 访 ...

  3. Linux 内核中的宏定义

    Linux 内核中的宏定义 rtoax 日期 内核版本:linux-5.10.13 注释版代码:https://github.com/Rtoax/linux-5.10.13 __attribute__ ...

  4. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  5. 简单谈一点linux内核中套接字的bind机制--数据结构以及端口确定

    众所周知,创建一个套接字可以bind到一个特定的ip地址和端口,实际上套接字这一概念代表了TCP/IP协议栈的应用层标识,协议栈中的应用层就是通过一个ip地址和一个端口号标识的,当然这仅仅是对于TCP ...

  6. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  7. 如何放出Linux内核中的链表大招

    前言 上回,我们说到Linux内核中max()宏的终极奥义,Linux内核链表也不甘示弱,那么接下来,让我们看看Linux内核中的链表大招. 如何放出Linux内核中的链表大招 前言 一.链表简介 ( ...

  8. Linux内核中max()宏的奥妙何在?(一)

    Linux内核中max()宏的奥妙何在?(一) 1.max()宏那点事 在Linux内核中,有这样四个比较大小的函数,如下: max(x,y) //两个数求最大值 min(x,y) //两个数求最小值 ...

  9. Linux内核中max()宏的奥妙何在?(二)——大神Linus对这个宏怎么看?

    最新max()宏 上回,我们在<Linux内核中max()宏的奥妙何在?(一)>一文中说到,在3.18.34版Linux内核源码中的max()宏,采用了GCC的扩展特性,可以避免一些错误. ...

最新文章

  1. Java项目:高校学生社团活动管理系统(java+springboot+freemark+jpa+mysql)
  2. 阿里云面向企业效率的云上产品全解析——云呼叫中心
  3. python迭代器高级例子
  4. python怎么读文件里的某一行-python读取txt文件并取其某一列数据的示例
  5. oracle树结构查询----connect by语法详解
  6. pmd代码安全扫描工具
  7. Hibernate隐藏的宝石:pooled-lo优化器
  8. [linux 技巧] 使用 screen 管理你的远程会话(转载)
  9. python判断汉字偏胖_写了2年python,知道 if __name__ == '__main__' 什么意思吗?
  10. linux pdf 编辑图层,PDF SDK DEMO
  11. 软考资料合集/软考真题合集(软件设计师/网络工程师/系统分析师/系统架构师/软件测评师/程序员等)
  12. 贝叶斯判别分析,Python代码分类讲解
  13. gpt分区android系统备份,win10 (GPT+UEFI)利用GHOST进行备份还原系统迁移
  14. [安洵杯 2019]easy misc 1
  15. 【Linux】安装系统的时候遇到grub-install Error 问题的解决,超详细傻瓜式教程
  16. 2017年7月最新全国行政规划数据库
  17. 英语拼读规则28条(必知)
  18. 谷歌浏览器自动翻译当前网页
  19. 【论文阅读】深度学习去雾1——论文精选
  20. 自学java成功率高吗,为什么自学Java编程成功率那么低?

热门文章

  1. linux下固态硬盘ssd优化
  2. python如何爬有道翻译_如何利用Python网络爬虫来获取有道翻译翻译接口--手机版的哦!...
  3. Coinlist要闻:即将到来的以太坊合并的风险有多大?
  4. java 获取本周第一天
  5. 《日语综合教程》第七册 第五課 みやこ人と都会人
  6. IT人员必学最基础知识(四)——补充总结
  7. Linux进程(上)
  8. long自动转为float类型
  9. NLP | textCNN textRNN 图文详解及代码
  10. 【C++】DBL_MAX