linux 线程机制?linux系统有哪些

大家好,今天给各位分享linux 线程机制的一些知识,其中也会对linux系统有哪些进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!

linux线程是如何进行切换的

进程调度与切换是操作系统核心功能之一,涉及进程管理、上下文切换、中断处理、栈操作与系统调用等知识,理解进程切换需要掌握多个知识点。以三个内核线程为例,当系统时钟中断发生时,中断处理函数会检查是否有进程需要调度。若0号线程先运行,那么接下来会发生什么?这问题涉及调度机制、中断、内核抢占、新进程调度与上下文切换。

在不支持内核抢占的Linux内核中,即使0号线程需要调度,内核不会立即调度线程1或线程2。只有在用户态中断或系统调用后检查是否需要调度。反之,在支持内核抢占的Linux内核中,中断返回时会检查当前进程是否需要调度。若需要,调度器将选择下一个进程,并进行上下文切换。

内核线程创建时会设置PC寄存器与SP寄存器,PC指向ret_from_fork汇编函数,SP指向pt_regs栈框。ARM64处理器操作具体为设置PC寄存器为ret_from_fork,SP寄存器指向pt_regs栈框。

Linux内核的时钟系统包含硬件与运行机制。硬件由RTC与OS时钟组成,OS时钟由定时/计数芯片产生,用于操作系统控制时间。时钟中断在每次时钟滴答后执行,负责处理时间信息与调度决策。中断与调度流程在支持内核抢占的Linux内核中略有不同,中断返回时检查是否需要调度,选择下一个进程并执行。

Linux的调度程序旨在合理分配系统资源,包括CPU、内存、文件与打印机等。调度算法选择需要综合考虑进程的运行时间、优先级、实时性与紧迫性。进程调度时机与依据包括当前进程状态、nice值、counter与实时性因素。调度程序运行时,依据进程的权值选择值得运行的进程。

Linux中调度程序是一个核心函数,如schedule(),根据进程权值决定进程执行。在多处理器系统(SMP)中,除了计算加权平均运行时间外,其他SMP相关部分主要由goodness()函数体现。

内核线程在创建时进行特定设置与保存,用于上下文切换与中断处理。调度程序选择进程时,考虑进程的状态、nice值、counter与实时性因素,依据权值决定进程运行。Linux内核中的时间单位是时钟滴答,用于衡量进程运行时间与调度决策。

关于linux下fork()函数机制

多线程程序设计的概念早在六十年代被提出,八十年代中期Unix系统引入多线程机制后,多线程编程得到广泛应用。fork函数是Unix系统的重要成果,它简化了多进程管理,为程序员提供方便。

在Linux下,进程有代码段、堆栈段和数据段组成,这些段用于程序代码、返回地址、局部变量以及全局变量等数据的存储。不同进程间不能共享堆栈段和数据段以避免冲突。

Linux的进程控制主要通过fork和exec函数族实现。fork函数创建一个进程的完全副本,返回值指示当前进程是父进程还是子进程。exec族函数替换当前进程,执行新程序,进程ID不变。典型用法包括创建进程副本进行并发操作或替换当前进程执行新程序。

fork函数在Linux下通过复制进程创建新进程,新进程称为子进程,与父进程完全相同,但有例外情况:子进程有唯一PID,与现有进程组ID不匹配;子进程的父进程ID与父进程ID相同;子进程不继承父进程的内存锁和信号量调整;子进程不会从父进程继承未完成的异步I/O操作或上下文。

exec函数族用于替换当前进程,执行新程序。execve系统调用用于替换进程,参数包括新程序文件名、参数列表和环境变量。exec函数家族包括:execl、execlp、execle、execv、execve和execvp,区别在于指定程序文件名方式、参数传递和是否显式指定环境变量。

调用exec前,进程打开的描述符通常保持打开,除非使用fcntl禁止FD_CLOEXEC描述符标志。inetd服务器利用了这一特性。以下为使用fork和exec的代码示例:

c

#include

#include

#include

int main(){

int pid= fork();//创建新进程

if(pid== 0){//子进程

printf("I am the child process, ID:%d\n", getpid());

printf("Now executing a new program...\n");

execlp("ls","ls", NULL);//执行新程序

exit(0);

} else{//父进程

printf("I am the parent process, ID:%d\n", getpid());

printf("Child process ID:%d\n", pid);

sleep(10);//等待子进程执行完成

}

return 0;

}

linux下线程属性常用操作有哪些

LinuxThread的线程机制

LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy(Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。

1.线程描述数据结构及实现限制

LinuxThreads定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量 __pthread_handles来描述和引用进程所辖线程。在__pthread_handles中的前两项,LinuxThreads定义了两个全局的系统线程:__pthread_initial_thread和__pthread_manager_thread,并用 __pthread_main_thread表征__pthread_manager_thread的父线程(初始为 __pthread_initial_thread)。

struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它一个元素,实际上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、 p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列 pthread_create()之后形成的__pthread_handles数组将如下图所示:

图2 __pthread_handles数组结构

新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。

LinuxThreads遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在 LinuxThreads的实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个:

每进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的操作数,LinuxThreads与POSIX一致,定义 PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024(PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(字节)。

2.管理线程

"一对一"模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建(__clone())并启动管理线程。

在一个进程空间内,管理线程与其他线程之间通过一对"管理管道(manager_pipe[2])"来通讯,该管道在创建管理线程之前创建,在成功启动了管理线程之后,管理管道的读端和写端分别赋给两个全局变量__pthread_manager_reader和 __pthread_manager_request,之后,每个用户线程都通过__pthread_manager_request向管理线程发请求,但管理线程本身并没有直接使用__pthread_manager_reader,管道的读端(manager_pipe[0])是作为__clone()的参数之一传给管理线程的,管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。

创建管理线程的流程如下所示:

(全局变量pthread_manager_request初值为-1)

图3创建管理线程的流程

初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id, 2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子线程的概念应该当作子进程来理解。)

__pthread_manager()就是管理线程的主循环所在,在进行一系列初始化工作后,进入while(1)循环。在循环中,线程以2秒为 timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就是创建manager的主线程)是否已退出,如果已退出就退出整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children()清理。

然后才是读取管道中的请求,根据请求类型执行相应操作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。

3.线程栈

在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。

用户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属性仅在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同时,用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK方式和用户自定义方式。

在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配8MB空间(i386系统缺省的最大栈空间大小,如果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访问区。该8M空间的功能分配如下图:

图4栈结构示意

低地址被保护的页面用来监测栈溢出。

对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。

不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。

4.线程id和进程id

每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。

阅读剩余
THE END