文件结构
1 | lib-pthread-embedded/ |
结构
主要引用unikraft中的uksched、uklock、uksignal几个库在封装,形成了pthread-embedded。
其中可以看到几个头文件与uksched中的同名,例如sched.h,pthread.h等。解决同名问题,遂采用了#include_next<>来引入头文件。
但看pthread-embedded和unikraft的工程文件,会发现pthread-embedded中很多函数都找不到声明。此时查看makefile.uk文件可以看到,似乎是引入了RWTH-OS/pthread-embedded
中的一些文件。
因此pthread-embedded是基于unikraft提供的一些接口以及RWTH-OS/pthread-embedded这两个库实现的一个线程库。
数据结构
这部分介绍了pthread-embedded中比较重要的数据结构。其中pte_thread_data的数据注释是比较清晰的。
1 | typedef struct pte_thread_data { |
- entry_point : 函数的入口,线程主体运行的入口。
- argv : 传递给entry_point的参数
- uk_thread : unikraft中与之11对应的线程
- tls : 线程局部存储
- start_sem : 线程启动的信号量
- cancel_sem : 线程取消的信号量
- done : 标记线程状态,非0代表线程为退出态
1 | static const void *PTE_CAPSULE_MAGIC = &PTE_CAPSULE_MAGIC; |
PTE_CAPSULE_MAGIC是一个指向自身的Magic Number。
&PTE_CAPSULE_MAGIC是一个指向PTE_CAPSULE_MAGIC的指针,是一个内存地址。因此这是一个指向自身的指针。
pet_entry_capsule主要用于封装程序入口,以及要传递的参数。
以上具体的作用将在后面提到。
1 | typedef struct pte_thread_data *pte_osThreadHandle; |
这三个作为处理器,在thread-embedded中一般作为参数传递使用,且传进去以后也强制类型转化成原类型。
初始化
pte_osInit主要对一些全局的变量进行声明,其中的一些函数在RWTH-OS/pthread-embedded中被实现,并且通过makefile链接进来。查阅后发现主要是对锁、内存等变量的声明、分配。
1 | pte_osResult pte_osInit(void) |
信号处理
1 |
|
kill在linux中默认用作终止进程,但是也可以用作向某一进程发送信号量。在pthread-embedded中直接调用了uksignal中的接口。在这pthread-embedded非常常见,可以说pthread就是基于unikraft提供的接口才实现的。
线程
线程创建
pte_osThreadCreate调用了Unikraft中的uksched API创建了线程,
1 | pte_osResult pte_osThreadCreate(pte_osThreadEntryPoint entry_point, |
其中将函数的入口(entry_point)和函数参数(argv)使用结构体pte_entry_capsule封装,该结构体在创建线程时,与unikraft中的uk_thread的参数arg进行绑定。而pte_osThreadHandle则是uk_thread中的prv参数。
除了传入pte_entry_capsule,还传入了PTE_CAPSULE_MAGIC。此时,如果是通过pte_osThreadCreate函数创建的线程,那么此时在uk_thread中的func参数与之对应的是PTE_CAPSULE_MAGIC,也就是函数自身。
回到uk_thread_create_attr这个函数
1 |
|
两个线程结构体的参数间的对应关系如下表(可能存在错误)所示。
pthread-embedded | 在函数中传递时 | uksched |
---|---|---|
argv | 不确定是否被封装进capsule中 | |
entry_point | 不确定是否被封装进capsule中 | |
capsule | data | arg |
ptd结构体 | 无 | prv |
uk_thread | 无 | uk_thread结构体 |
PTE_CAPSULE_MAGIC | function | entry |
线程启动
申请线程启动信号量,以下函数应该是作为通用的线程函数入口。
1 | static void uk_stub_thread_entry(void *argv) |
1 | // 通过uksched API 创建一个线程。 |
在前面我们知道,如果是通过pte_osThreadCreate创建的函数,此时func应是PTE_CAPSULE_MAGIC;
arg是前面封装好的capsule。如果不是通过pte_osThreadCreate创建的函数(这里没看到具体的常见,猜测应是系统内部自带的线程,也需要统一纳入到pthread-embedded进行管理),则不是这样。因此就产生了以下分歧。
在uk_thread中的参数说明 | 不通过uksched API创建的线程 | 通过uksched API创建的线程 |
---|---|---|
prv | pte_osThreadEntryPoint | pte_osThreadEntryPoint |
arg | 应传入函数的参数 | pte_entry_capsule |
entry | 函数入口 | 无实际意义 |
以上函数则希望完成对二者的兼容,统一纳入管理。
函数入口的统一模板如下所示。
1 | static void uk_stub_thread_entry(void *argv) |
对于通过uksched API创建的线程
主要就是拿到pte_entry_capsule封装的函数指针和函数参数,从uk_thread中放到pte中,同时将uk_thread中的函数入口改为uk_stub_thread_entry。
对于不通过uksched API创建的线程
直接取到uk_thread中的函数入口指针和参数,装入pte中,同时将uk_thread中的函数入口改为uk_stub_thread_entry。
二者的主要区别就是通过不同方式创建的线程,在内核中uk_thread中一些属性代表的值会有所区别。
锁与信号量
与kill函数类似,这部分就是对unikraft相关接口的再封装。这部分的相关函数,同质化比较严重,也比较简单,就不展开细讲。
1 | pte_osResult pte_osMutexLock(pte_osMutexHandle h) |
总结
pthread-embedded是一个非常简洁的线程实现。它主要基于unikraft提供的接口和一个来自github上的库,构建了一套属于自己的线程管理逻辑。这种设计方式,和linux内核中的nptl之于pthread的关系非常相似。