打印
[其他ST产品]

ST源码分析-st_thread_create - 弦外之音

[复制链接]
2525|96
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本文 主要分析 st_thread_create() 函数的内部实现。
st_thread_create 流程图如下:




使用特权

评论回复
沙发
9dome猫|  楼主 | 2023-4-21 12:09 | 只看该作者
现在开始一行一行代码分析 st_thread_create

  _st_thread_t *thread;
  _st_stack_t *stack;
  void **ptds;
  char *sp;

使用特权

评论回复
板凳
9dome猫|  楼主 | 2023-4-21 12:09 | 只看该作者
上面代码声明了 4 个变量 ,变量 ptds 的全称应该是 pthread_data_register(线程的寄存器缓存),sp 的全称 是 stack point。

使用特权

评论回复
地板
9dome猫|  楼主 | 2023-4-21 12:09 | 只看该作者
/* Adjust stack size */
if (stk_size == 0)
  stk_size = ST_DEFAULT_STACK_SIZE;
stk_size = ((stk_size + _ST_PAGE_SIZE - 1) / _ST_PAGE_SIZE) * _ST_PAGE_SIZE;
stack = _st_stack_new(stk_size);
if (!stack)
  return NULL;

使用特权

评论回复
5
9dome猫|  楼主 | 2023-4-21 12:09 | 只看该作者
上面开始申请 栈内存,ST_DEFAULT_STACK_SIZE 是 (64*1024) ,也就是 64Kb 默认内存。还有一个 宏定义 _ST_PAGE_SIZE,定义如下:

#define _ST_PAGE_SIZE                   (_st_this_vp.pagesize)

使用特权

评论回复
6
9dome猫|  楼主 | 2023-4-21 12:09 | 只看该作者
_st_this_vp.pagesize 是用 getpagesize() 获取操作系统的内存页大小。所以下面的这行代码,又乘,又除,实际上是为了得出 操作系统内存页大小 的倍数。

stk_size = ((stk_size + _ST_PAGE_SIZE - 1) / _ST_PAGE_SIZE) * _ST_PAGE_SIZE;

使用特权

评论回复
7
9dome猫|  楼主 | 2023-4-21 12:13 | 只看该作者
为什么 stk_size 要是 操作系统 内存页大小的 倍数 ,这个涉及到内存对齐,虚拟内存跟物理内存的映射原理,通俗来说,是为了让内存使用更加高效

使用特权

评论回复
8
9dome猫|  楼主 | 2023-4-21 12:13 | 只看该作者
然后 就调 _st_stack_new() 函数 来申请内存。在 讲 _st_stack_new 函数的内部逻辑 之前,先普及一下 mmap 跟 calloc 的区别。

mmap 的一个常见的用法,是把文件映射到内存,例如把 2G 的大文件映射到内存,然后写内存就相当于写文件,但是并不会占用 2G 的物理内存。mmap 他这个映射是虚拟的,如果不使用文件映射,可以把 fd 设置为 -1 就是 匿名映射。而 calloc 函数申请的是实实在在的物理内存,物理内存的使用量会增加。

使用特权

评论回复
9
9dome猫|  楼主 | 2023-4-21 12:13 | 只看该作者
mmap 例子:

#include <unistd.h>
#include <sys/mman.h>

int main() {
    void* vaddr = mmap(NULL, 1024 * 1024 * 1024 * 1, PROT_READ | PROT_WRITE, 34, -1, 0);
    sleep(600);
    return 0;
}

使用特权

评论回复
10
9dome猫|  楼主 | 2023-4-21 12:15 | 只看该作者
calloc 例子:

#include <unistd.h>
#include <stdlib.h>

int main() {
    void* vaddr = calloc(1, 1024 * 1024 * 1024 * 1);
    sleep(600);
    return 0;
}

使用特权

评论回复
11
9dome猫|  楼主 | 2023-4-21 12:18 | 只看该作者
执行命令编译:

gcc -o calloc calloc.c

使用特权

评论回复
12
9dome猫|  楼主 | 2023-4-21 12:18 | 只看该作者
然后用 top -p 100054 查看指定进程的情况,如下图:

使用特权

评论回复
13
9dome猫|  楼主 | 2023-4-21 12:18 | 只看该作者
回到 _st_stack_new() 函数的分析,流程图如下:

使用特权

评论回复
14
9dome猫|  楼主 | 2023-4-21 12:18 | 只看该作者
从流程图可以看出,_st_stack_new 函数主要有两个重点:

1,用 calloc 来申请一块内存,_st_stack_t 这个结构体是 56 字节,因为 calloc 会初始化为 0,会导致 commit。所以物理内存的使用是实实在在增加了 56 字节。代码如下:

if ((ts = (_st_stack_t *)calloc(1, sizeof(_st_stack_t))) == NULL)
    return NULL;

使用特权

评论回复
15
9dome猫|  楼主 | 2023-4-21 12:19 | 只看该作者
2,用 _st_new_stk_segment 来申请栈的实际内存,_st_new_stk_segment 函数比较简单,就是对 mmap 封装一下。

// REDZONE 等于一个内存页大小
ts->vaddr_size = stack_size + 2*REDZONE + extra;
ts->vaddr = _st_new_stk_segment(ts->vaddr_size);
st_thread_create() 函数传给 _st_stack_new() 函数的 stack_size 参数是 65536 ,也就是 64 字节,但是不会立马使用 64 kb 的物理内存,因为用的是 mmap。不是 calloc。注意,实际上 他还加了 2*REDZONE,多了两个内存页大小。为什么要冗余两个内存页大小,请看文章《ST源码分析-内存保护》。

使用特权

评论回复
16
9dome猫|  楼主 | 2023-4-21 12:22 | 只看该作者
_st_free_stack 的逻辑本文不会讲解太多,因为这是入门教程,第一次调 _st_stack_new() 是不会跑进去 _st_free_stack 条件的,不会跑进去的逻辑本文不会讲解太多,力求简单。

使用特权

评论回复
17
9dome猫|  楼主 | 2023-4-21 12:22 | 只看该作者
_st_stack_new 运行后之后,整个栈内存的结构是这样的...

使用特权

评论回复
18
9dome猫|  楼主 | 2023-4-21 12:22 | 只看该作者
调用_st_stack_new() 拿到这么一个 栈结构之后,回到 st_thread_create() 的逻辑,代码如下:

#if defined (MD_STACK_GROWS_DOWN)
  sp = stack->stk_top;
#ifdef

使用特权

评论回复
19
9dome猫|  楼主 | 2023-4-21 12:23 | 只看该作者
因为我的环境 栈是向下压的,所以 MD_STACK_GROWS_DOWN 定义了。

sp = sp - (ST_KEYS_MAX * sizeof(void *));
ptds = (void **) sp;

使用特权

评论回复
20
9dome猫|  楼主 | 2023-4-21 12:23 | 只看该作者
上面的代码,ST_KEYS_MAX 等于 16,(void *) 在 32位占 4字节,在 64 位占 8字节。所以上面的代码逻辑是 从之前申请的内存里面拿出 16 * 8 个字节的内存 存储协程的私有数据,这块内存在 ST 代码里没有使用,是留给使用者自由发挥的。想存什么就存什么变量 ptds 的全称是 private_data,ptds 指向数据的开始地址。

sp = sp - sizeof(_st_thread_t);
thread = (_st_thread_t *) sp;

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

1407

帖子

2

粉丝