找到目录 c 的文章 4 篇.


python的Greenlet模块源码分析

Posted on 2015-01-26 14:58:26 c

摘要:

协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程可以在运行期间的某个点上暂停执行,并在恢复运行时从暂停的点上继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 协程可以被认为是一种用户空间线程,与传统的线程相比,有2个主要的优点:

与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常小。

协程可以认为是一种用户态的线程,与系统提供的线程不同点是,它需要主动让出CPU时间,而不是由系统进行调度,即控制权在程序员手上。既然看成是用户态线程,那必然要求程序员自己进行各个协程的调度,这样就必须提供一种机制供编写协程的人将当前协程挂起,即保存协程运行场景的一些数据,调度器在其他协程挂起时再将此协程运行场景的数据恢复,以便继续运行。这里我们将协程运行场景的数据称为上下文。

阅读全文

AT&T 汇编和 GCC 内联汇编简介

Posted on 2015-01-25 18:37:38 c

AT&T 汇编和 GCC 内联汇编简介

对一个应用程序员来讲,了解汇编不是必需的,更少有手写纯汇编的需求。但是如果能了解些基本的汇编知识,对程序调试和一些语言特性的理解是大有裨益的。本文介绍 AT&T 语法的汇编的要点以及 GCC 使用的内联汇编(inline assembly)的使用。

AT&T 汇编

AT&T 汇编是 GCC 所采用的语法,要点:

  • 寄存器名以 ‘%’ 为前缀:%eax;
  • 立即数以 ‘$’ 为前缀:$0x80;
  • 指令格式 instrunction src, dest,分别为指令名,源操作数,目的操作数,例如 mov $0, %rax;
  • 操作数的宽度以指令名后缀指名,或者由操作数宽度隐式推出:单字节 b,双字节 w,四子节 l,八字节 q。例如 movb $0, (rax);
  • 相对寻址/寄存器寻址/索引寻址均由 seg:off(base, index, scale) 标识。seg 为段寄存器;off 为偏移量;base 为基址寄存器;index 为索引寄存器;scale 为索引的偏移粒度。seg/off/index/scale 均可省略:seg 默认由操作数的属性决定,数据寻址为 ds,代码寻址为 cs;off 默认为 0;index 默认为 0;scale 默认为 1。比如,有个 Message 的结构体数组,该结构体 大小为 16 字节,len 成员的偏移量为 8,数组起始地址保存在 %rbx,元素索引保存在 %rcx,那么,movq 8(rax, rcx, 16), %rdx 将数组的第 %rcx 个元素的 len 成员 load 到 %rdx 中。

通用寄存器(x86_64):

  • rax, eax, ax, ah, al;
  • rbx, ebx, bx, bh, bl;
  • rcx, ecx, cx, ch, cl;
  • rdx, edx, dx, dh, dl;
  • rsi, esi, si;
  • rdi, edi, di;
  • rbp, ebp;
  • rsp, esp;
  • r8-r15;
  • xmm0-xmm7;
  • st0-st7;
  • fs;

X86_64 下 ABI 调用约定:

  • 整型参数(包括整数、指针等),由左至右,分别使用 rdi, rsi, rdx, rcx, r8, r9 传递参数,超过 6 六个参数时,多余参数压栈传递;
  • 浮点型参数使用 xmm0-xmm7 传递,多余参数压栈传递;
  • 整型返回值使用 rax:rdx,浮点型返回值使用 xmm0:xmm1,long double 使用 st0:st1;
  • 结构体参数的传递较为复杂,可能由寄存器或者压栈传递,参考 AMD64 ABI 文档;
  • 结构体返回值,由调用方提供栈空间,并将起始地址通过 rdi 传入;
  • rbp 『一般』为栈帧(stack frame)基址;
  • rsp 为栈顶地址;
  • Linux 中使用 fs 实现 TLS(Thread Local Storage);
  • rbx, rbp, rsp, r12-r15 为 callee-saved registers,即调用其他函数不会改变此类寄存器内容。其他寄存器为 caller-saved,如有必要,函数调用方需要自行保存;

GCC 内联汇编

内联汇编允许在 C/C++ 代码中嵌入汇编代码,以优化关键代码或者使用架构特有的指令。内联汇编的基本格式如下:

asm [volatile] ( <assembler template>
    : ["constraints"(var)] [,"constraints"(var)]  /* output operands */
    : ["constraints"(var)] [,"constraints"(var)]  /* input operands */
    : ["register"] [,"register"] [,"memory"]      /* clobbered registers */
    );

中括号中为可选部分,尖括号为必选部分。圆括号内由 ‘:’ 分割为四个部分:

  • asm 为 GCC 扩展关键字,为防止和代码标识符冲突,可使用 asm 代替;
  • volatile 告诉编译器,不要试图优化圆括号中的汇编代码;
  • 『assembler template』内为指令模板,其中的操作数可以使用 %n 样式的占位符(placeholder),n 为 0-9 的数字,编译器会使用后面输入/输出部分代入。如果代码中直接使用寄存器,需要使用两个 ‘%%’, 例如 ‘%%eax’;
  • 第二和第三部分分别为输出/输入操作数说明;输入/输出部分是 C/C++ 代码和汇编代码交互的界面,用来指名汇编代码中可以使用哪些变量以及汇编代码的计算结果保存到哪些变量。变量可以为多个,以逗号分割,按照出现的顺序分别编号,汇编代码中使用该编号来引用这个变量,比如 %0 为第一个变量。每个变量的指示格式为 “contraints”(var),其中 constraints 限定了汇编代码中变量 var 可以使用的寄存器(输入变量)或者将哪个寄存器保存到变量 var 中(输出)。constraints 中可以指名多个寄存器,编译器按照实际情况任意分配其中一个。
  • 第四部分为修改说明(clobbered list)。clobbered list 中可以列举寄存器名,这些寄存器在代码中是显式使用的,而不是由编译器自动分配或者在输入/输出指名的。特殊地,“memory” 告诉编译器,汇编代码中显式使用内存地址/全局变量访问了内存,执行该段汇编之后,所有之前的寄存器需要重新加载。

常用的 constraints 为:

  • r, 分别下面子列表中寄存器的任意一个来保存 var 变量,相当于 abcdSD:
    • a, %rax, %eax, %ax, %al
    • b, %rbx, %ebx, %bx, %bl
    • c, %rcx, %ecx, %cx, %cl
    • d, %rdx, %edx, %dx, %dl
    • S, %rsi, %esi, %si
    • D, %rdi, %edi, %di
  • q, 相当于 abcd
  • m, 内存操作数
  • digit, 使用和第 #digit 个相同的寄存器
  • f, 使用一个浮点寄存器

输出 constraints 中需要下面至少一个『修饰符』(constraints modifier)作为前缀:

  • =, 此操作数仅作为输出,之前的内容可以抛弃;
  • +, 此操作数同时作为输入和输出。

下面看几个示例:

asm ("":::); //~ nothing
asm ("incl %%eax\n\t":::"eax"); //~ access register directly
asm ("movq $1, %0\n\t" : "=m"(var)); //~ write 1 to var
asm ("mov %0, %%eax\n\t" : : "m"(var)); //~ read from var to eax
//~ read a to eax, read b to either ebx|ecx|edx|edi|esi, add it to eax, write back eax to a
asm ("addl %1, %0\n\t" : "+a"(a) : "r"(b));
asm ("incq global_var\n\t" :::"memory"); //~ access global_var directly
asm ("incl %0\n\t" : "+q"(var)); //~ read var to either eax|ebx|ecx|edx, increase it, write it back to var
asm ("incl %0\n\t" : "=q"(var) : "0"(var)) //~ the same as above, constraint 0 means using the same register
asm ("incl %[__var__]\n\t" : [__var__]"+q"(var)); //~ use user-defined placeholder

最后一个示例使用了用户自定义的占位符,通常在输入输入变量较多的情况下使用,省得逐个地对应。

在汇编中调用 printf:

#include <stdio.h>
int
main()
{
  char *fmt = "Hello, %s\n";
  char *s = "World";
  int ret = 0;
  asm (" callq printf\n\t"
      : "=a"(ret)
      : "D"(fmt), "S"(s));
  printf("ret: %d\n", ret);
  return 0;
}

在汇编中进行系统调用:

int
sys_write(int fd, const char *buf, size_t n)
{
  int ret;
  asm (
      "syscall\n\t"
      : "=a"(ret)
      : "0"(1), "D"(fd), "S"(buf), "d"(n)
      );
  return ret;
}
 
int
main()
{
  char *s = "Hello, World\n";
  printf("%d\n", sys_write(fileno(stdout), s, strlen(s)));
  return 0;
}

参考资料

  • Professional Assembly Language, Richard Blum. 貌似是唯一一本以 AT&T 语法讲解汇编语言的了。
  • Programming From The Ground Up, Jonathan Bartlett, 如果上一本是以编程讲汇编的,这一本就是以汇编讲编程的了。
  • System V Application Binary Interface, AMD64 架构下的 System V ABI, 也是 Linux 使用的 ABI.

libev简介

Posted on 2014-02-12 07:36:22 c

摘要:

libev是Marc Lehmann用C写的高性能事件循环库。通过libev,可以灵活地把各种事件组织管理起来,如:时钟、io、信号等。libev在业界内也是广受好评,不少项目都采用它来做底层的事件循环。

阅读全文

fcntl的close-on-exec标志

Posted on 2014-02-10 09:30:13 c

摘要:

每个文件描述符都有一个close-on-exec标志。默认情况下,这个标志最后一位被设置为 0。这个标志符的具体作用在于当开辟其他进程调用exec()族函数时,在调用exec函数之前为exec族函数释放对应的文件描述符。

阅读全文