找到归档 2015-01 的文章 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.

不要习惯了黑暗,就为黑暗辩护

Posted on 2015-01-14 05:23:36 life

如果天空总是黑暗的,那就摸黑生存;如果发出声音是危险的,那就保持沉默;如果自觉无力发光,那就蜷伏于墙角。 但不要习惯了黑暗就为黑暗辩护;也不要为自己的苟且而得意;不要嘲讽那些比自己更勇敢的人们。我们可以卑微如尘土,但不可扭曲如蛆虫。 –曼德拉


go程序调试总结

Posted on 2014-10-04 03:51:00 golang

摘要:

首先需要注意的是:golang1.3之后的版本,对于支持gdb调试存在很大的问题。产生这个问题的原因是,golang的runtime没有完整的被gdb支持。

最新比较完整支持gdb调试的版本是golang 1.2.2,但是也有个别问题存在。

为什么会出现以上种种问题,golang官网给出的解释是:

GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo. As a consequence, although GDB can be useful in some situations, it is not a reliable debugger for Go programs, particularly heavily concurrent ones. Moreover, it is not a priority for the Go project to address these issues, which are difficult. In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success.

翻译一下:

GDB不能很好的理解GO程序。堆栈管理,线程,而且runtime包含了非常不一样的执行模式,这不是GDB期望的,他们会扰乱调试器,即使go程序是使用gccgo编译的。结果就是,虽然GDB在某些场合下是有用的,但是对go程序来说并不是一个可靠的调试器。尤其是在大量并发的时候。而且,这不是Golang项目优先考虑的事情,这很困难。总而言之,下面的操作手册,只是当GDB正常工作的时候,引导你如何使用GDB,不能保证总是成功。

并且从google group讨论组和stackoverflow中,可以看到golang的多个版本对于GDB的支持都有这样那样的问题。 不过既然官方的手册都这么说了,我们也只有在合适的场合使用GDB吧。

默认情况下,编译过的二进制文件已经包含了 DWARFv3 调试信息,只要 GDB7.1 以上版本都可以进行调试。 在OSX下,如无法执行调试指令,可尝试用sudo方式执行gdb。

在编译go程序的时候,需要关闭内联优化:** -gcflags “-N -l”**。可以在go get/build/test的时候指定这个参数。

有两种方式可以下断点:

  • gdb命令运行之后,使用break file:lineno
  • 使用runtime.BreakPoint()
阅读全文