当前位置:首页 > 天道酬勤 > 正文内容

linux查看线程数(linux线程进程调度)

张世龙2022年01月15日 05:41天道酬勤830

作者:labuladong

资料来源:https://segment fault.com/a/1190000038488005

说到进程,也许面试中最常见的问题是线程和进程之间的关系。 那么,我先说一下答案吧。 在Linux系统上,进程和线程几乎没有区别。

Linux中的进程是一种数据结构,使您可以理解文件描述符、重定向和管道命令的基本工作方式。 最后,让我们看看为什么从操作系统的角度看线程和进程几乎没有区别。

一、进程是什么

首先,抽象地说,我们的计算机就是这个:

这个大矩形表示计算机的内存空间,其中小矩形表示进程,左下角的圆形表示磁盘,右下角的图形表示输入/输出设备,如鼠标、键盘和显示器。 另外,请注意内存空间被分割为两个块。 上半部分表示用户空间,下半部分表示内核空间。

用户空间包含用户进程使用的资源。 例如,在程序代码中打开数组时,该数组始终存在用户空间;内核区域包含内核进程必须加载的系统资源。 这些资源通常不允许用户访问。 但是,请注意,也有共享内核区域资源(如动态链接库)的用户进程。

我们可以用c语言编写hello程序,编译得到可执行文件,在命令行上运行可以打印hello world,然后退出程序。 在操作系统级别,创建了一个新的进程,用于将编译的可执行文件读取到内存区域,然后运行并退出。

你编译的那个可执行程序只是文件,不是进程。 可执行文件必须加载到内存中,包装在进程中才能实际运行。 进程是依赖操作系统创建的。 每个进程都有自己的属性。 例如,进程编号(PID )、进程状态、打开的文件等。 进程创建后,装入你的程序,你的程序运行到系统中。

那么,操作系统是如何制定流程的呢? 对于操作系统,进程是数据结构。 让我们直接看看Linux的源代码。

结构任务_结构{

//进程状态

长期状态;

//虚拟存储器结构体

结构mm _结构* mm;

//进程编号

pid_t pid;

//指向父进程的指针

结构任务_结构_ _ rcu * parent;

//子进程列表

struct list_head children;

//用于存储文件系统信息的指针

struct fs_struct *fs;

//包含打开进程的文件指针的数组

struct files_struct *files;

(; 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错误。

我们可以再画一次:

iaoimg.com/origin/pgc-image/5498335c6ef54b07b0805025f6b25d44?from=pc">

对于一般的计算机,输入流是键盘,输出流是显示器,错误流也是显示器,所以现在这个进程和内核连了三根线。因为硬件都是由内核管理的,我们的进程需要通过「系统调用」让内核进程访问硬件资源。

PS:不要忘了,Linux 中一切都被抽象成文件,设备也是文件,可以进行读和写。

如果我们写的程序需要其他资源,比如打开一个文件进行读写,这也很简单,进行系统调用,让内核把文件打开,这个文件就会被放到 files 的第 4 个位置:

明白了这个原理, 输入重定向 就很好理解了,程序想读取数据的时候就会去 files[0] 读取,所以我们只要把 files[0] 指向一个文件,那么程序就会从这个文件中读取数据,而不是从键盘:

$ command < file.txt

同理, 输出重定向 就是把 files[1] 指向一个文件,那么程序的输出就不会写入到显示器,而是写入到这个文件中:

$ command > file.txt

错误重定向也是一样的,就不再赘述。

管道符其实也是异曲同工,把一个进程的输出流和另一个进程的输入流接起一条「管道」,数据就在其中传递,不得不说这种设计思想真的很优美:

$ cmd1 | cmd2 | cmd3

到这里,你可能也看出「Linux 中一切皆文件」设计思路的高明了,不管是设备、另一个进程、socket 套接字还是真正的文件,全部都可以读写,统一装进一个简单的 files 数组,进程通过简单的文件描述符访问相应资源,具体细节交于操作系统,有效解耦,优美高效。

三、线程是什么

首先要明确的是,多进程和多线程都是并发,都可以提高处理器的利用效率,所以现在的关键是,多线程和多进程有啥区别。

为什么说 Linux 中线程和进程基本没有区别呢,因为从 Linux 内核的角度来看,并没有把线程和进程区别对待。

我们知道系统调用 fork() 可以新建一个子进程,函数 pthread() 可以新建一个线程。 但无论线程还是进程,都是用 task_struct 结构表示的,唯一的区别就是共享的数据区域不同 。

换句话说,线程看起来跟进程没有区别,只是线程的某些数据区域和其父进程是共享的,而子进程是拷贝副本,而不是共享。就比如说, mm 结构和 files 结构在线程中都是共享的,我画两张图你就明白了:

所以说,我们的多线程程序要利用锁机制,避免多个线程同时往同一区域写入数据,否则可能造成数据错乱。

那么你可能问, 既然进程和线程差不多,而且多进程数据不共享,即不存在数据错乱的问题,为什么多线程的使用比多进程普遍得多呢 ?

因为现实中数据共享的并发更普遍呀,比如十个人同时从一个账户取十元,我们希望的是这个共享账户的余额正确减少一百元,而不是希望每人获得一个账户的拷贝,每个拷贝账户减少十元。

当然,必须要说明的是,只有 Linux 系统将线程看做共享数据的进程,不对其做特殊看待,其他的很多操作系统是对线程和进程区别对待的,线程有其特有的数据结构,我个人认为不如 Linux 的这种设计简洁,增加了系统的复杂度。

在 Linux 中新建线程和进程的效率都是很高的,对于新建进程时内存区域拷贝的问题,Linux 采用了 copy-on-write 的策略优化,也就是并不真正复制父进程的内存空间,而是等到需要写操作时才去复制。 所以 Linux 中新建进程和新建线程都是很迅速的 。

作者:labuladong

出处:https://segmentfault.com/a/1190000038488005

扫描二维码推送至手机访问。

版权声明:本文由花开半夏のブログ发布,如需转载请注明出处。

本文链接:https://zhangshilong.cn/work/43830.html

分享给朋友:

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。