Libco 소스 코드 읽기 (2) : 코 루틴의 핵심 데이터 구조

    코 루틴의 생성 및 전환은 사용자가 제어하므로 코 루틴이 전환 될 때 컨텍스트 정보를 저장하는 방법이 섹션에서는 코 루틴을 구현하기위한 libco의 주요 데이터 구조를 소개합니다.

1. 코 루틴 엔티티 : stCoRoutine_t

2. 코 루틴 컨텍스트 정보 : coctx_t

3. 프라이빗 스택 및 공유 스택 : stack_mem & stShareStack_t

4. 스레드 환경 : stCoRoutineEnv_t

5. 코 루틴 속성 : stCoRoutineAttr_t


1. 코 루틴 엔티티 : stCoRoutine_t

    이 구조는 실제로 코 루틴의 주요 구조로, 코 루틴 관련 데이터를 저장하고, 각 코 루틴은 코 루틴의 개인 데이터와 코 루틴이 전환 될 때 컨텍스트 정보를 저장하는 stCoRoutine_t에 해당합니다. 이 구조는 코 루틴 생성 하기 위해 co_create ()가 호출 될 때마다 초기화됩니다.

struct stCoRoutine_t
{
	stCoRoutineEnv_t *env; // 协程的执行环境, 运行在同一个线程上的各协程是共享该结构
	pfn_co_routine_t pfn;  // 一个函数指针, 指向实际待执行的协程函数 
	void *arg;             // 函数的参数
	coctx_t ctx;           // 用于协程切换时保存CPU上下文(context)的,即 esp、ebp、eip 和其他通用寄存器的值
	char cStart;           // 协程是否执行过resume
	char cEnd;             // 协程是否执行结束
	char cIsMain;          // 是否为主协程修改
	char cEnableSysHook;   // 此协程是否hook库函数,即用自己实现的函数替代库函数
	char cIsShareStack;    // 是否开启共享栈模式

	void *pvEnv;           // 保存程序系统环境变量的指针

	//char sRunStack[ 1024 * 128 ];
	stStackMem_t* stack_mem; // 协程运行时的栈内存

	//save satck buffer while confilct on same stack_buffer;
	char* stack_sp;         // 保存栈顶指针sp
	unsigned int save_size; // 保存协程的栈的buffer的大小
	char* save_buffer;      // 使用共享栈模式时,用于保存该协程的在共享栈中的内容

	stCoSpec_t aSpec[1024];
};

2. 코 루틴 컨텍스트 정보 : coctx_t

     이 구조는 실제로 레지스터의 값인 코 루틴의 컨텍스트를 저장합니다. C 또는 C ++의 어떤 함수도 레지스터에 직접 닿을 수 없으므로이 매개 변수를 작동 할 때 약간의 어셈블리 코드를 삽입해야합니다.

struct coctx_t
{
#if defined(__i386__)
	void *regs[ 8 ];  // X86架构下有8个通用寄存器
#else
	void *regs[ 14 ]; // x64位下有16个寄存器,这里保存14个
#endif
	size_t ss_size;  // 栈的大小
	char *ss_sp;     // 栈顶指针esp
	
};


// 32 bit
// | regs[0]: ret |
// | regs[1]: ebx |
// | regs[2]: ecx |
// | regs[3]: edx |
// | regs[4]: edi |
// | regs[5]: esi |
// | regs[6]: ebp |
// | regs[7]: eax |  = esp

// 64 bit
//low | regs[0]: r15 |
//    | regs[1]: r14 |
//    | regs[2]: r13 |
//    | regs[3]: r12 |
//    | regs[4]: r9  |
//    | regs[5]: r8  | 
//    | regs[6]: rbp |
//    | regs[7]: rdi |
//    | regs[8]: rsi |
//    | regs[9]: ret |  //ret func addr
//    | regs[10]: rdx |
//    | regs[11]: rcx | 
//    | regs[12]: rbx |
//hig | regs[13]: rsp |

   x86-64의 16 개의 64 비트 레지스터는 % rax, % rbx, % rcx, % rdx, % esi, % edi, % rbp, % rsp, % r8- % r15입니다. 그들 중 :

  • % rax는 함수의 반환 값으로 사용됩니다.
  • 스택 맨 위를 가리키는 % rsp 스택 포인터 레지스터.
  • % rdi, % rsi, % rdx, % rcx, % r8, % r9는 첫 번째 매개 변수와 두 번째 매개 변수에 차례로 대응하는 함수 매개 변수로 사용됩니다.
  • % rbx, % rbp, % r12, % r13, % 14, % 15는 데이터 저장소로 사용되며 수신자 보호 규칙을 따르고 아무렇게나 사용하고 하위 기능을 호출하기 전에 백업하여 수정되지 않도록합니다. ;
  • % r10, % r11은 데이터 저장소로 사용되며 발신자 보호 규칙을 따르고 사용하기 전에 원래 값을 저장하십시오.

두 가지 생소한 용어 인 발신자 보호 및 수신자 보호를 살펴 보겠습니다.

  • 호출자 보호 :이 레지스터에 저장된 값은 호출자 (상위 함수)가 직접 백업해야 함을 의미합니다. 그렇지 않으면 자식 함수가 이러한 레지스터를 직접 사용하여 무자비하게 덮어 씁니다. 백업하는 방법? 물론 스택 (pushl)을 푸시하고 하위 함수 호출이 완료 될 때까지 기다린 다음 스택 (popl)을 통해 복원합니다.
  • 수신자 보호 : 수신자 (하위 함수)가 호출자를 백업 할 방법 (상위 함수)을 찾아야 함을 의미합니다.

3. 프라이빗 스택 및 공유 스택 : stack_mem & stShareStack_t

    stack_mem은 코 루틴의 프라이빗 스택을 실행하는 구조이고 stShareStack_t는 공유 스택의 구조입니다. Libco에는 코 루틴 스택에 대한 두 가지 전략이 있습니다.

  • 하나는 코 루틴이 스택을 할당한다는 것입니다. 이는 기본 구성이기도하지만 기본 크기는 128KB이고 1024 코 루틴이 128MB이면 1024 * 1024 코 루틴은 128GB로 "수천 개의 "Million Connections"는 멀리 떨어져 있습니다. 그리고이 공간에는 분명히 많은 간격이 있으며 많은 코 루틴은 1KB 미만 만 사용할 수 있습니다. 이는 분명히 큰 낭비입니다.
  • 또 다른 전략은 스택을 공유하는 것입니다. 즉, 모든 고 루틴이 동일한 스택을 사용하고 각 고 루틴은 자체 스택 내용을 저장하기 위해 버퍼를 사용합니다.이 버퍼의 크기는 고정되어 있지 않으므로 메모리를 절약 할 수 있습니다. libco가 코 루틴을 전환 할 때 먼저 공유 스택의 콘텐츠를 스왑 할 코 루틴 엔티티의 구조 버퍼에 복사하고 공유 스택에 스왑 할 코 루틴 엔티티의 구조에있는 버퍼 콘텐츠를 복사합니다. 이러한 스레드의 모든 코 루틴은 실제로 런타임에 동일한 스택을 사용하는데,이를 공유 스택이라고합니다. 공유 스택 모드를 사용하는 경우 코 루틴 생성시 co_create에 두 번째 파라미터를 지정해야합니다.이 방법은 여러 코 루틴이 동일한 스택을 공유하지만 코 루틴이 전환 될 때 사용 된 스택 공간을 복사해야합니다.
struct stStackMem_t
{
	stCoRoutine_t* ocupy_co; // 执行时占用的那个协程实体,也就是这个栈现在是那个协程在用
	int stack_size;          // 当前栈上未使用的空间
	char* stack_bp;          // stack_buffer + stack_size
	char* stack_buffer;      // 栈的起始地址,当然对于主协程来说这是堆上的空间

};

struct stShareStack_t
{
	unsigned int alloc_idx; // stack_array中我们在下一次调用中应该使用的那个共享栈的index
	int stack_size;         // 共享栈的大小,这里的大小指的是一个stStackMem_t*的大小
	int count;              // 共享栈的个数,共享栈可以为多个,所以以下为共享栈的数组
	stStackMem_t** stack_array; // 栈的内容,这里是个数组,元素是stStackMem_t*
};

4. 스레드 환경 : stCoRoutineEnv_t

      stCoRoutineEnv_t는 매우 중요한 구조입니다.이 구조는 모든 데이터 중에서 가장 특별합니다. 왜냐하면 스레드의 모든 코 루틴이 공유하는 구조이기 때문입니다. 즉, 동일한 스레드에 의해 생성 된 모든 코 루틴의이 구조 포인터가 동일한 것을 가리 킵니다. 하나의 데이터. 일부 코 루틴 스케줄링 관련 데이터를 저장합니다. 물론 libco에 의해 구현 된 비대칭 코 루틴에는 실제로 스케줄링 전략이 없기 때문에 스케줄링 호출을 꺼려합니다. 코 루틴 스위치가 코 루틴 또는 스레드를 호출하는 것입니다. 코 루틴.

struct stCoRoutineEnv_t
{
	stCoRoutine_t *pCallStack[ 128 ]; // 协程的调用栈
	int iCallStackSize;               // 调用栈的栈顶指针
	stCoEpoll_t *pEpoll;              // epoll的一个封装结构

	//for copy stack log lastco and nextco
	stCoRoutine_t* pending_co;       // 目前占用共享栈的协程
	stCoRoutine_t* ocupy_co;         // 与pending在同一个共享栈上的上一个协程
};
  • pCallStack : 코 루틴이 특수 함수로 간주되면이 pCallStack은 이러한 함수의 호출 체인 스택을 저장합니다. 비대칭 코 루틴의 가장 큰 특징은 코 루틴간에 명확한 호출 관계가 있다는 것입니다. 일부 문헌에서도 코 루틴을 시작하는 것을 호출이라고하고 코 루틴을 일시 중단하는 것을 리턴이라고합니다. 비대칭 코 루틴 메커니즘 하에서 호출 된 코 루틴은 호출자 코 루틴으로 만 복귀 할 수 있으며 이러한 종류의 호출 관계는 엉망이 될 수 없으므로 호출 체인을 저장해야합니다.
  • pending_co 및 ocupy_co : 공유 스택에있는 데이터의 복사본을 줄이기 위해 마지막 스위치의 코 루틴 스택 복사본이 코 루틴 및 중첩 호출을 일시 중단했습니다. 공유 스택 모드를 사용하지 않는 경우 pending_co 및 ocupy_co는 모두 널 포인터입니다.

5. 코 루틴 속성 : stCoRoutineAttr_t

    coroutine 속성의 stCoRoutineAttr_t 구조 는 스택의 크기와 공유 스택 사용 여부를 표시합니다.

struct stCoRoutineAttr_t
{
	int stack_size;   // 协程的私有栈或者共享栈大小
	stShareStack_t*  share_stack; // 指向协程的共享栈
	stCoRoutineAttr_t()
	{
		stack_size = 128 * 1024;
		share_stack = NULL;
	}
}__attribute__ ((packed));

 

추천

출처blog.csdn.net/MOU_IT/article/details/114682146