『壹』 linux中的内核线程和普通进程有哪些区别
内核线程是拥有root权限的,所以攻击的话就是攻击内核的漏洞~
普通线程与普通进程是普通用户执行的程序,一般下是没有root权限的~方便系统的安全管理~
『贰』 麻烦解释一下linux下进程和线程有什么区别和联系,linux下多线程和多进程通信的实现方法,请通俗解释
兄弟看到你这么高的分我就找了些资料:也算是对昨天学的知识总结一下吧
一、先说概念不管是windows还是linux下的进程和线程概念都是一样的,只是管理进程和线程的方式不一样,这个是前提,到时候你可别问我windows下进程和线程啊。这个涉及到操作系统原理。下面给你解答。
说道进程不得不提作业这个名词 ,我想兄弟你电脑里不会有一个程序吧对不?当你的系统启动完毕后你看看你的任务管理器里是不是有很多进程呢?那么多程序是怎么调如内存呢?能理解吗?这里要明白程序和进程的关系,程序是你磁盘上的一个文件,当你需要它时进入内存后才成为进程,好比QQ在磁盘上就是一个文件而已,只有进入了内存才成为进程,进程是活动的。QQ要扫描你文件啊,记录你聊天记录啊,偷偷上传个啥东西什么的你也不知道对不,他是活动的。这个能明白吗?
再看作业,这个作业可不是你写作业的那个作业啊。系统一看好家伙你个QQ那么大的家伙你想一下子进入内存啊?没门!慢慢来嘛,系统就把QQ程序分为好几块,这几块不能乱分的,要符合自然结构就是循环啦选择啦这样的结构,你把人家循环结构咔嚓截断了,怎么让人家QQ运行啊?这就是作业要一块一块的进入内存,同时要为作业产生JCB(JOB CONTROL BLOCK)作业控制块,你进入内存不能乱跑啊,要听系统的话,你要是进入系统自己的内存。框一下,内存不能读写 对话框就出来了,严重点直接蓝脸给你!你懂得。这是window下的,linux下直接给你报错!没事了就!所一系统通过jcb控制进程。JCB包含了进程号优先级好多内容,你打开你的windows任务管理器看看进程是不是有好多属性啊?那就是PCB(PRCESS,CONTROL BLOCK)同理作业也包含那些内容只是多少而已。下面写出进程特点:
1、进程是分配计算机资源最小的单位。你想啊人是要用程序干活的吧?你把程序调入内存成了就成了进程,所以说进程是分配资源的最小单位。你在linux下打开终端输入top命令看是不是有好多进程?
2、进程有操作系统为作业产生。有“父进程”产生“子进程”之间是父子关系,并可以继续向下产生“子进程”。还拿QQ来说,你双击QQ.exe。QQ启动了输入账号密码打开主界面了。这时候你要聊天,QQ进程赶紧产生个“儿子”说 “儿子你去陪主人聊天去吧。这样子进程产生了。突然你想看美女要传照片这时候那个”儿子“有”生“了一个”儿子“说”儿子“你去传照片。那个“儿子领到任务去传照片了。这时你想关了QQ,QQ提示你说”你还有个“儿子”和“孙子”还在干活呢你真要结束吗?你蒽了确定。QQ对他“儿子”(你聊天窗口)说:”儿子啊对不起了,主人要关闭我你也不能活啊“咔嚓一下”儿子“死了,儿子死之前对他儿子说:“儿子啊你爷爷不让我活了,你也别活了咔嚓孙子也死了。最后世界安静了。这就是进程的父子关系。能明白吗?记住:进程之活动在内存中。不能使用CPU,只管分配资源。
再说线程:线程也产生在内存中并且在内存中存在相当长的时间,但它的活动区域主要在CPU中,并且运行和灭亡都存在于CPU中,可以这么说,线程是程序中能被系统调度进入CPU中最小程序单位,它能直接使用进程分配的CPU的资源。
还拿QQ来说当你要传文件时QQ总要判断一下文件的扩展名吧,ok这时那个”儿子“赶紧对它爸爸说我需要一个线程判断扩展名QQ赶紧对一个管这个的线程说:”快点去CPU里计算下那个扩展名是什么然后向主人报告计算完了就“死了”消亡了,但是它的线程还在内存中!还等着你下一次传文件然后计算然后消亡!
线程之间是相互独立的。一个在CPU,一个在内存里还能有关系吗对不?CPU在每一个瞬间只能进入一个线程,当线程进入CPU时立即产生一个新的线程,新线程仍停留在内存中,就好比上面那个传文件还会等着你再传文件再计算扩展名。
线程相对线程是独立的,但它在内存中并不是独立的,这就好比你不开QQ能用QQ传输文件吗?它只存在与进程分配的资源中,也就是说计算扩展名这个线程只能停留在QQ这个进程中,不能跑到别的进程里!!相当于程序产生了新的进程和线程,进程向CPU申请资源,再有线程来使用,他们都是为程序服务的只是分工不同!
因为你没提问linux下是怎么管理进程和线程的所以我就不回答了,这个问题我建议你还是看看《笨兔兔的故事》里面讲到了linux是怎么管理进程和线程的。挺幽默的比我说得还好。
你第二个问题说实话我回答不了你!我想你现在连进程和线程还没理解第二个你更理解不了了你说对不?我猜的其实你用C/C++不管是在windows下编程还是在Linux下编程思想都是一样的对吧,如果你理解了在windows下线程间通信,在linux更没问题了!
参考资料:黑客手册2009合订本非安全第一二季244页,245页,328页,329页,398页,399页
浅谈操作系统原理 (一 二三)
ubuntu中文论坛 笨兔兔的故事
http://forum.ubuntu.org.cn/viewtopic.php?f=120&t=267518
希望我的回答你能理解
『叁』 linux中内核线程与用户线程在调度上有什么区别
用户级实现线程时,内核调度是以进程为单位的,内核并不知道用户级线程的存回在,因此答某个用户级线程的阻塞即会引起整个进程的阻塞。
内核级线程阻塞时,内核完全可以调度同进程内的其它线程运行,也就是没有阻塞整个线程
『肆』 linux内核线程怎么设置优先级
Linux内核的三种调度策略:
1,SCHED_OTHER 分时调度策略,
2,SCHED_FIFO实时调度策略,先到先服务。一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃
3,SCHED_RR实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平
Linux线程优先级设置
首先,可以通过以下两个函数来获得线程可以设置的最高和最低优先级,函数中的策略即上述三种策略的宏定义:
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR支持优先级的使用,他们分别为1和99,数值越大优先级越高。
设置和获取优先级通过以下两个函数:
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
例如以下代码创建了一个优先级为10的线程:
struct sched_param
{
int __sched_priority; //所要设定的线程优先级
};
例:创建优先级为10的线程
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_RR);
param.sched_priority = 10;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(xxx , &attr , xxx , xxx);
pthread_attr_destroy(&attr);
『伍』 Linux 进程、线程和CPU的关系,cpu亲和性
1、物理CPU数:机器主板上实际插入的cpu数量,比如说你的主板上安装了一块8核CPU,那么物理CPU个数就是1个,所以物理CPU个数就是主板上安装的CPU个数。
2、物理CPU核数:单个物理CPU上面有多个核,物理CPU核数=物理CPU数✖️单个物理CPU的核
3、逻辑CPU核数:一般情况,我们认为一颗CPU可以有多个核,加上intel的超线程技术(HT), 可以在逻辑上再分一倍数量的CPU core出来。逻辑CPU核数=物理CPU数✖️单个物理CPU的核*2
4、超线程技术(Hyper-Threading):就是利用特殊的硬件指令,把两个逻辑CPU模拟成两个物理CPU,实现多核多线程。我们常听到的双核四线程/四核八线程指的就是支持超线程技术的CPU。
1、并行:两件(多件)事情在同一时刻一起发生。
2、并发:两件(多件)事情在同一时刻只能有一个发生,由于CPU快速切换,从而给人的感觉是同时进行。
3、进程和线程
进程是资源分配的最小单位,一个程序有至少一个进程。线程是程序执行的最小单位。一个进程有至少一个线程。
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
4、单核多线程:单核CPU上运行多线程, 同一时刻只有一个线程在跑,系统进行线程切换,系统给每个线程分配时间片来执行,看起来就像是同时在跑, 但实际上是每个线程跑一点点就换到其它线程继续跑。
5、多核多线程:每个核上各自运行线程,同一时刻可以有多个线程同时在跑。
1、对于单核:多线程和多进程的多任务是在单cpu交替执行(时间片轮转调度,优先级调度等),属于并发
2、对于多核:同一个时间多个进程运行在不同的CPU核上,或者是同一个时间多个线程能分布在不同的CPU核上(线程数小于内核数),属于并行。
3、上下文切换:上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。上下文切换过程中的信息被保存在进程控制块(PCB-Process Control Block)中。PCB又被称作切换帧(SwitchFrame)。上下文切换的信息会一直被保存在CPU的内存中,直到被再次使用。
CPU 亲和性(affinity)就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。这样可以减少上下文切换的次数,提高程序运行性能。可分为:自然亲和性和硬亲和性
1、自然亲和性:就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。Linux调度器缺省就支持自然CPU亲和性(natural CPU affinity): 调度器会试图保持进程在相同的CPU上运行。
2、硬亲和性:简单来说就是利用linux内核提供给用户的API,强行将进程或者线程绑定到某一个指定的cpu核运行。Linux硬亲和性指定API:taskset .
taskset [options] mask command [arg]...
taskset [options] -p [mask] pid
taskset 命令用于设置或者获取一直指定的 PID 对于 CPU 核的运行依赖关系。也可以用 taskset 启动一个命令,直接设置它的 CPU 核的运行依赖关系。
CPU 核依赖关系是指,命令会被在指定的 CPU 核中运行,而不会再其他 CPU 核中运行的一种调度关系。需要说明的是,在正常情况下,为了系统性能的原因,调度器会尽可能的在一个 CPU 核中维持一个进程的执行。强制指定特殊的 CPU 核依赖关系对于特殊的应用是有意义的
CPU 核的定义采用位定义的方式进行,最低位代表 CPU0,然后依次排序。这种位定义可以超过系统实际的 CPU 总数,并不会存在问题。通过命令获得的这种 CPU 位标记,只会包含系统实际 CPU 的数目。如果设定的位标记少于系统 CPU 的实际数目,那么命令会产生一个错误。当然这种给定的和获取的位标记采用 16 进制标识。
0x00000001
代表 #0 CPU
0x00000003
代表 #0 和 #1 CPU
0xFFFFFFFF
代表 #0 到 #31 CPU
-p, --pid
对一个现有的进程进行操作,而不是启动一个新的进程
-c, --cpu-list
使用 CPU 编号替代位标记,这可以是一个列表,列表中可以使用逗号分隔,或者使用 "-" 进行范围标记,例如:0,5,7,9
-h, --help
打印帮助信息
-V, --version
打印版本信息
如果需要设定,那么需要拥有 CAP_SYS_NICE 的权限;如果要获取设定信息,没有任何权限要求。
taskset 命令属于 util-linux-ng 包,可以使用 yum 直接安装。
『陆』 「图文结合」Linux 进程、线程、文件描述符的底层原理
开发十年经验总结,阿里架构师的手写Spring boot原理实践文档
阿里架构师的这份:Redis核心原理与应用实践,带你手撕Redis
Tomcat结构原理详解
说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案: 在 Linux 系统中,进程和线程几乎没有区别 。
Linux 中的进程其实就是一个数据结构,顺带可以理解文件描述符、重定向、管道命令的底层工作原理,最后我们从操作系统的角度看看为什么说线程和进程基本没有区别。
首先,抽象地来说,我们的计算机就是这个东西:
这个大的矩形表示计算机的 内存空间 ,其中的小矩形代表 进程 ,左下角的圆形表示 磁盘 ,右下角的图形表示一些 输入输出设备 ,比如鼠标键盘显示器等等。另外,注意到内存空间被划分为了两块,上半部分表示 用户空间 ,下半部分表示 内核空间 。
用户空间装着用户进程需要使用的资源,比如你在程序代码里开一个数组,这个数组肯定存在用户空间;内核空间存放内核进程需要加载的系统资源,这一些资源一般是不允许用户访问的。但是注意有的用户进程会共享一些内核空间的资源,比如一些动态链接库等等。
我们用 C 语言写一个 hello 程序,编译后得到一个可执行文件,在命令行运行就可以打印出一句 hello world,然后程序退出。在操作系统层面,就是新建了一个进程,这个进程将我们编译出来的可执行文件读入内存空间,然后执行,最后退出。
你编译好的那个可执行程序只是一个文件,不是进程,可执行文件必须要载入内存,包装成一个进程才能真正跑起来。进程是要依靠操作系统创建的,每个进程都有它的固有属性,比如进程号(PID)、进程状态、打开的文件等等,进程创建好之后,读入你的程序,你的程序才被系统执行。
那么,操作系统是如何创建进程的呢? 对于操作系统,进程就是一个数据结构 ,我们直接来看 Linux 的源码:
task_struct 就是 Linux 内核对于一个进程的描述,也可以称为「进程描述符」。源码比较复杂,我这里就截取了一小部分比较常见的。
我们主要聊聊 mm 指针和 files 指针。 mm 指向的是进程的虚拟内存,也就是载入资源和可执行文件的地方; files 指针指向一个数组,这个数组里装着所有该进程打开的文件的指针。
先说 files ,它是一个文件指针数组。一般来说,一个进程会从 files[0] 读取输入,将输出写入 files[1] ,将错误信息写入 files[2] 。
举个例子,以我们的角度 C 语言的 printf 函数是向命令行打印字符,但是从进程的角度来看,就是向 files[1] 写入数据;同理, scanf 函数就是进程试图从 files[0] 这个文件中读取数据。
每个进程被创建时, files 的前三位被填入默认值,分别指向标准输入流、标准输出流、标准错误流。我们常说的「文件描述符」就是指这个文件指针数组的索引 ,所以程序的文件描述符默认情况下 0 是输入,1 是输出,2 是错误。
我们可以重新画一幅图:
对于一般的计算机,输入流是键盘,输出流是显示器,错误流也是显示器,所以现在这个进程和内核连了三根线。因为硬件都是由内核管理的,我们的进程需要通过「系统调用」让内核进程访问硬件资源。
PS:不要忘了,Linux 中一切都被抽象成文件,设备也是文件,可以进行读和写。
如果我们写的程序需要其他资源,比如打开一个文件进行读写,这也很简单,进行系统调用,让内核把文件打开,这个文件就会被放到 files 的第 4 个位置,对应文件描述符 3:
明白了这个原理, 输入重定向 就很好理解了,程序想读取数据的时候就会去 files[0] 读取,所以我们只要把 files[0] 指向一个文件,那么程序就会从这个文件中读取数据,而不是从键盘:
同理, 输出重定向 就是把 files[1] 指向一个文件,那么程序的输出就不会写入到显示器,而是写入到这个文件中:
错误重定向也是一样的,就不再赘述。
管道符其实也是异曲同工,把一个进程的输出流和另一个进程的输入流接起一条「管道」,数据就在其中传递,不得不说这种设计思想真的很巧妙:
到这里,你可能也看出「Linux 中一切皆文件」设计思路的高明了,不管是设备、另一个进程、socket 套接字还是真正的文件,全部都可以读写,统一装进一个简单的 files 数组,进程通过简单的文件描述符访问相应资源,具体细节交于操作系统,有效解耦,优美高效。
首先要明确的是,多进程和多线程都是并发,都可以提高处理器的利用效率,所以现在的关键是,多线程和多进程有啥区别。
为什么说 Linux 中线程和进程基本没有区别呢,因为从 Linux 内核的角度来看,并没有把线程和进程区别对待。
我们知道系统调用 fork() 可以新建一个子进程,函数 pthread() 可以新建一个线程。 但无论线程还是进程,都是用 task_struct 结构表示的,唯一的区别就是共享的数据区域不同 。
换句话说,线程看起来跟进程没有区别,只是线程的某些数据区域和其父进程是共享的,而子进程是拷贝副本,而不是共享。就比如说, mm 结构和 files 结构在线程中都是共享的,我画两张图你就明白了:
所以说,我们的多线程程序要利用锁机制,避免多个线程同时往同一区域写入数据,否则可能造成数据错乱。
那么你可能问, 既然进程和线程差不多,而且多进程数据不共享,即不存在数据错乱的问题,为什么多线程的使用比多进程普遍得多呢 ?
因为现实中数据共享的并发更普遍呀,比如十个人同时从一个账户取十元,我们希望的是这个共享账户的余额正确减少一百元,而不是希望每人获得一个账户的拷贝,每个拷贝账户减少十元。
当然,必须要说明的是, 只有 Linux 系统将线程看做共享数据的进程 ,不对其做特殊看待 ,其他的很多操作系统是对线程和进程区别对待的,线程有其特有的数据结构,我个人认为不如 Linux 的这种设计简洁,增加了系统的复杂度。
在 Linux 中新建线程和进程的效率都是很高的,对于新建进程时内存区域拷贝的问题,Linux 采用了 -on-write 的策略优化,也就是并不真正复制父进程的内存空间,而是等到需要写操作时才去复制。 所以 Linux 中新建进程和新建线程都是很迅速的 。
『柒』 Linux 进程管理之进程调度与切换
我们知道,进程运行需要各种各样的系统资源,如内存、文件、打印机和最
宝贵的 CPU 等,所以说,调度的实质就是资源的分配。系统通过不同的调度算法(Scheling Algorithm)来实现这种资源的分配。通常来说,选择什么样的调度算法取决于资源分配的策略(Scheling Policy)。
有关调度相关的结构保存在 task_struct 中,如下:
active_mm 是为内核线程而引入的,因为内核线程没有自己的地址空间,为了让内核线程与普通进程具有统一的上下文切换方式,当内核线程进行上下文切换时,让切换进来的线程的 active_mm 指向刚被调度出去的进程的 active_mm(如果进程的mm 域不为空,则其 active_mm 域与 mm 域相同)。
在 linux 2.6 中 sched_class 表示该进程所属的调度器类有3种:
进程的调度策略有5种,用户可以调用调度器里不同的调度策略:
在每个 CPU 中都有一个自身的运行队列 rq,每个活动进程只出现在一个运行队列中,在多个 CPU 上同时运行一个进程是不可能的。
运行队列是使用如下结构实现的:
tast 作为调度实体加入到 CPU 中的调度队列中。
系统中所有的运行队列都在 runqueues 数组中,该数组的每个元素分别对应于系统中的一个 CPU。在单处理器系统中,由于只需要一个就绪队列,因此数组只有一个元素。
内核也定义了一下便利的宏,其含义很明显。
Linux、c/c++服务器开发篇-------我们来聊聊进程的那些事
Linux内核 进程间通信组件的实现
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括 C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享
在分析调度流程之前,我们先来看在什么情况下要执行调度程序,我们把这种情况叫做调度时机。
Linux 调度时机主要有。
时机1,进程要调用 sleep() 或 exit() 等函数进行状态转换,这些函数会主动调用调度程序进行进程调度。
时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4 是一样的。
时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查 need_resched 的值,如果必要,则调用调度程序 schele() 主动放弃 CPU。
时机4 , 如前所述, 不管是从中断、异常还是系统调用返回, 最终都调用 ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。
Linux 的调度程序是一个叫 Schele() 的函数,这个函数来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等。
从代码分析来看,Schele 主要完成了2个功能:
进程上下文切换包括进程的地址空间的切换和执行环境的切换。
对于 switch_mm 处理,关键的一步就是它将新进程页面目录的起始物理地址装入到寄存器 CR3 中。CR3 寄存器总是指向当前进程的页面目录。
switch_to 把寄存器中的值比如esp等存放到进程thread结构中,保存现场一边后续恢复,同时调用 __switch_to 完成了堆栈的切换。
在进程的 task_struct 结构中有个重要的成分 thread,它本身是一个数据结构 thread_struct, 里面记录着进程在切换时的(系统空间)堆栈指针,取指令地址(也就是“返回地址”)等关键性的信息。
关于__switch_to 的工作就是处理 TSS (任务状态段)。
TSS 全称task state segment,是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息。
linux 为每一个 CPU 提供一个 TSS 段,并且在 TR 寄存器中保存该段。
linux 中之所以为每一个 CPU 提供一个 TSS 段,而不是为每个进程提供一个TSS 段,主要原因是 TR 寄存器永远指向它,在任务切换的适合不必切换 TR 寄存器,从而减小开销。
在从用户态切换到内核态时,可以通过获取 TSS 段中的 esp0 来获取当前进程的内核栈 栈顶指针,从而可以保存用户态的 cs,esp,eip 等上下文。
TSS 在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务的执行。
在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到 TR(任务寄存器)所指定的任务的 TSS 中;然后,下一任务的 TSS 被装入 TR;最后,从 TR 所指定的 TSS 中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在 TSS 中保存任务现场各寄存器状态的完整映象,实现任务的切换。
因此,__switch_to 核心内容就是将 TSS 中的内核空间(0级)堆栈指针换成 next->esp0。这是因为 CPU 在穿越中断门或者陷阱门时要根据新的运行级别从TSS中取得进程在系统空间的堆栈指针。
thread_struct.esp0 指向进程的系统空间堆栈的顶端。当一个进程被调度运行时,内核会将这个变量写入 TSS 的 esp0 字段,表示这个进程进入0级运行时其堆栈的位置。换句话说,进程的 thread_struct 结构中的 esp0 保存着其系统空间堆栈指针。当进程穿过中断门、陷阱门或者调用门进入系统空间时,处理器会从这里恢复期系统空间栈。
由于栈中变量的访问依赖的是段、页、和 esp、ebp 等这些寄存器,所以当段、页、寄存器切换完以后,栈中的变量就可以被访问了。
因此 switch_to 完成了进程堆栈的切换,由于被切进的进程各个寄存器的信息已完成切换,因此 next 进程得以执行指令运行。
由于 A 进程在调用 switch_to 完成了与 B 进程堆栈的切换,也即是寄存器中的值都是 B 的,所以 A 进程在 switch_to 执行完后,A停止运行,B开始运行,当过一段时间又把 A 进程切进去后,A 开始从switch_to 后面的代码开始执行。
schele 的调用流程如下:
『捌』 Linux用户进程内核态执行,内核线程的关系问题
1、几乎所有的程序都要切换到内核态运行再返回用户态,用中断完成回的,因为在内核下答封装了一些东西,用户态下只是传入某些参数后调用内核态下的函数罢了,
2、进程有三态(执行态,就绪态,阻塞态),cpu任何时刻都只有一个进程在执行,so从用户态切换到内核态时,用户态下的进程就处于阻塞或就绪态了,至于从用户态切换到内核态执行哪个函数那就看你在用户态下执行的是什么函数了,比如在用户态下的lseek在内核下就是llseek了,不一样的。
3、这问题就是linux的内存管理了,这里就得提到三种地址(逻辑地址、线性地址、物理地址),这里我们提到的4G地址是逻辑地址,不是我们实际的物理地址,linux中一个进程用户占0-3G对应的内核占3G-4G部分
说得不是很清楚,这是比较复杂的内容,需要从头看起,单就这几个问题是不能搞懂linux的,最好还是系统的学习,不断的重复
『玖』 linux系统中线程同步实现机制有哪些
LinuxThread的线程机制
LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy ([email protected]) 负责开发完成,并已绑定在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分配和维护。