线程是哪些?按百度百科的定义:线程是操作系统才能进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一次序的执行流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。此文介绍Linux线程的基本逻辑,线程的主要数据结构,和线程的内核插口。
Linux线程基本逻辑
Linux内核在启动的时侯是没有线程概念的,当内核初始化完成后将启动一系列的线程,以后,CPU的执行流就绑定在一个线程中运行了。假如绑定的是进程中的线程,这么执行的是进程的代码,假如绑定的是内核线程,将执行内核的服务代码。
线程是CPU执行与调度最基本的单位,每一个线程创建之初都是内核线程;创建以后假如与具体的进程上下文绑定,那线程就成了用户线程;倘若此线程是进程的第一个线程,这么称之为进程的主线程;虽然,在CPU执行和调度中并没有本质上的区别。
在用户角度,执行一个个程序就是创建一个个进程的主线程。当主线程创建时,同时创建用户空间,打开输入输出资源,载入依赖库等。主线程创建好后开始运行,进程也就同时存在了,这时程序可以按照须要创建用户线程,用户线程创建好后,共享主线程的用户空间,文件资源,依赖库等,用户线程退出后,主线程不会退出linux基础教程,进程也不会退出。当主线程退出时,内核开始关掉打开的文件,释放相关资源,主线程退出后进程也就退出了。这时,虽然用户线程只执行到一半也无济于事了。
线程主要数据结构
线程结构体的数据元素多达几十项,全部学习和理解也没有必要,主要的数据结构如右图所示:
structtask_struct
线程结构体,包含整个线程相关的所有数据,上面的元素比较多,我们只谈谈和线程创建、调度相关的元素。当线程创建的时侯linux内核启动流程图linux视频教程,首先从高速显存kmem_cache中分配task_struct所需的空间,之后从显存分配器中再分配4个连续的页面作为堆栈区,接着从创建者那儿复制而不是从头创建大部份的数据,最后设置相应的线程调度器,这样线程对象就创建好了。当下一次发生调度的时侯,CPU就晓得假如调度此线程了。
structsched_class
此结构体定义了线程调度器使用的函数,与C++中的泛型一样,这种插口都是函数表针类型,由具体的调度器实现。诸如实时优先级调度器rt_sched_class和通常优先级调度器fair_sched_class,她们的实现文件分别在kernel/sched/rt.c和kernel/sched/fair.c中,我们谈到线程调度的时侯再进去看具体的代码。
structthread_struct
此结构体是线程相关上下文的具体实现,因为每种处理器对线程的运行方法不一样,所以每种CPU体系下都定义有各自的线程上下文结构体。arm64体系的定义如右图所示:
65-80行,保存相关寄存器的值。
83行,与线程相关的TLS寄存器的值。
线程内核插口
Linux线程的实现相当复杂,但使用却比较简单。使用插口封装成了几个函数,其他模块只需直接使用这种函数,例如只需调用一句宏即可,如图:
在须要使用线程的地方,定义一个线程函数:
int(*threadfn)(void*data)
之后调用kthread_run宏创建并运行线程,线程函数的返回值就是线程的返回值。
31行linux内核启动流程图,kthread_run宏即为线程的内核插口宏,其他模块只需直接调用。
threadfin,线程函数,即线程创建好后执行的函数。
data,线程参数,传入线程函数的参数,由调用者传入。
namefmt,线程的名子,参数及后续参数是printf风格的。内核线程的名子,可以在shell中使用ps命令查看到。
34,13行,同样是宏,最终调用kthread_create_on_node函数实现。
7行,__printf(4,5)是gcc扩充句型,展开后为__attribute__((format(printf,4,5))),使编译器检测函数申明和函数实际调用参数之间的低格字符串是否匹配。这儿,告诉编译器检测kthread_create_on_node函数的第4个和第5个参数是否按printf的格式使用,假如调用者没有按此格式调用,则编译报错。
8行,线程创建并运行的函数,用户也可以绕开插口宏,直接调用。此函数多了一个参数node,指定线程该分配的CPU节点,在SMP系统中,一般设置为NUMA_NO_NODE。
以上,介绍了Linux内核线程的基本逻辑,线程的主要数据结构,和线程的内核插口,线程的实际创建及调度另文再述。(armv8,kernel4.4)
相关文章
Linux工作队列workqueue源码剖析(四)
Linux工作队列workqueue源码剖析(三)
Linux系统调用源码剖析(四)
Linux系统调用源码剖析(三)