linux用户空间和内核exit的语义--linux没有线程

简介:

如果你在程序中调用了exit,那么很显然你的程序会退出,可是至于为何会退出那就是库的事情了,我为什么说只是库的事情而不关linux内核的事情呢?那是因为linux内核根本不管用户空间的行为策略。库的策略是什么?很简单的退出当前进程吗?如果是多线程的程序呢?多线程的程序它的行为又是什么呢?在我们探究库的行为以及探究库为何会有这样的行为之前首先谈谈内核对exit的实现sys_exit,在sys_exit中,我丝毫找不到关于退出线程的代码,按照常规的思考,如果sys_exit负责将本进程的所有线程都退出的话,那么合理的办法就是在本进程的signal结构体上append上一个SIGKILL信号,然后唤醒所有的线程的task_struct,因为一个进程的所有线程共享信号处理结构,因此每个被唤醒的线程的task_struct都会在信号处理时自己退出,这样看起来很不错,也很合理,如果是windows想到了这个主意,那么它肯定会采用的,可是这是linux。

linux中没有线程的概念,我这么说不是在说linux很落后吗?现代操作系统中都会有线程的实现。其实不然,在linux中,线程并不是一个操作系统的内秉性概念,在linux中只有进程,然后可以在进程的基础上轻松实现线程,这个意义上,线程仅仅是一个策略,一个由很多的实体概念组合而成的概念。其实在linux上,完全不能用传统的操作系统的标准来说明,比如线程,进程,然后又分了什么X程,这样岂不是越来越乱,每引入一个概念,操作系统的体系就要大动一番,在linux中就有一个执行绪的概念,该执行绪就是task_struct,你把它理解成进程也好,理解成线程也罢,其实它可以表达成任何可以执行的东西,在task_struct的基础上,我们可以垒砌很多外延的东西,比如进程,线程等等,在linux中,只有task_struct一个概念,进程,线程只是它的外延而已,task_struct和不同的特性组合就可以表示程不同的外延,比如,它和gid,uid等组合就是进程,它和线程组或者TGID组合就是线程。

理解了一行原则,exit的内核行为就十分简单了,既然内核原始的只有task_struct这一个执行绪概念,那么它就是应该有它的创建和销毁的内核api,并且在哲学意义上这两个内核api是对称的,我们很多人都了解linux的线程创建,其实也是用的fork,cone在内核不也是用fork实现的吗?创建一个现代操作系统的线程在linux中就是创建一个执行绪,也就是创建一个task_struct,那么do_fork就是干这个的,相反的,对于退出并没有多少人去关注,既然fork创建了一个task_struct,那么exit就是销毁一个task_struct,别的并不做什么,fork没有线程的概念,它只负责按照用户提供的参数创建一个task_struct,这里线程这个外延是通过参数体现的,既然参数可以赋予一个task_struct以线程的含义,那么fork中也就根据此含义对task_struct的字段进行了设置,以表示这是一个线程,在linux内核中并没有线程的概念,而仅有线程的外延,既然创建行为fork如此,那么销毁行为exit也是如此,如此一来就可以理解exit中根本就不可能有什么向本进程的所有线程发送退出信号一说,它只管销毁这个调用exit的执行绪的task_struct(其实是递减这个task_stuct的引用计数),而不管什么线程的概念,那么谁会去管线程的概念呢?当然是谁定义谁管了,比如Posix或者用户的其它库,内核将线程这个task_struct的外延导出给用户,那么用户就可以用这个外延的一系列特性以及行为准则来操作这个线程外延,故而用户库可以用内核提供的最小化的正交组合接口配上线程这个外延来组合成一个可以退出所有线程的接口,其实就是对于每一个线程调用其exit。

内核实现毕竟是内核实现,linux还是遵循posix的,因此它提供了一个系统调用sys_exit_group,之所以如此是因为这样的话,用户库就不必再费劲心机切入每个线程并且在每个线程调用exit了,当然这也不是linux内核所希望的。sys_exit_group中会调用zap_other_threads(current)来退出每个线程,不管怎样,exit_group的提供仅仅是为了遵循posix,而exit才是linux的设计中原汁原味的执行绪操作系统调用,其实在用户库里面完全可以用向所有的线程发送SIGKILL信号来实现exit。

举个例子来说明一切:

#include.h>

#include

#include.h>

#include

void direct_exit() //这个函数直接用系统调用实现了exit,即到了内核直接调用sys_exit

{

int a = 1,b = 0;

asm("movl %0,%%eax/n/t" /

"movl %1,%%ebx/n/t" /

"int $0x80/n/t" /

::"r" (a),"r" (b));

}

int handler( void *p)

{

while(1)

{

sleep(1);

printf("Sub thread is running/n");

}

}

int main(int argc, char* argv[])

{

int i = 0;

clone(handler, &i-1024, CLONE_SIGHAND|CLONE_VM|CLONE_THREAD, NULL);

while(1)

{

sleep(1);

printf("Main thread is running/n");

if(i++>15)

{

direct_exit();//直接退出,和线程没有关系,子线程handler继续运行,不受影响

//exit(); //调用库里面的exit,当然要遵循posix的线程语义,所有线程退出

}

}

return 0;

}

作为最后,为了使得本文的副标题不是空设,稍微谈一下linux的进程uid等特征,前面的文章说过,linux靠uid,gid实现了多用户,这个多用户是进程意义上的,如果按照本文前面说的概念和外延的观点来看的话,多用户只是为了实现多用户而必须的一个执行绪的参数而已,其实每个执行绪即task_struct都有一个uid和gid等信息而并不一定仅仅指进程,如下的例子可以证明:

int handler( void *p)

{

open("/root/b",O_CREATE);

perror("open b");

setuid(500);

seteuid(500);

open("/root/c",O_CREATE);

perror("open c");

}

int main(int argc, char* argv[])

{

int i = 0;

clone(handler, &i-1024, CLONE_SIGHAND|CLONE_VM|CLONE_THREAD, NULL);

if(getchar()=="w")

{

open("/root/a",O_CREATE);

perror("open a");

}

return 0;

}

在以上的例子中,以root用户运行这个代码,c的打开将失败,而a,b将成功,这里可以说明在不同的线程里面可以有不同的uid和gid,其实这里的执行绪已经不再是线程了,这个例子再次说明,在linux内核中没有线程的明确定义,再抽象一点其实也没有进程,而仅仅有执行绪而已,这个执行绪到底是什么,就看用户提供什么策略使他成为什么外延了。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273411


相关文章
|
11天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
45 4
|
5天前
|
算法 Linux 开发者
Linux内核中的锁机制:保障并发控制的艺术####
本文深入探讨了Linux操作系统内核中实现的多种锁机制,包括自旋锁、互斥锁、读写锁等,旨在揭示这些同步原语如何高效地解决资源竞争问题,保证系统的稳定性和性能。通过分析不同锁机制的工作原理及应用场景,本文为开发者提供了在高并发环境下进行有效并发控制的实用指南。 ####
|
13天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
41 9
|
12天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
34 6
|
13天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
32 5
|
13天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
13天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
32 2
|
10天前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
93 6
|
11天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
47 3
下一篇
无影云桌面