Linux进程、线程和文件描述符的底层原理解读
面试中最常见的问题就是线程和进程的关系,所以我们先来说说答案:在Linux系统中,几乎没有进程和线程之间的区别。
Linux中的进程实际上是一种数据结构。顺便可以了解文件描述符、重定向和管道命令的底层工作原理。最后我们看一下为什么从操作系统的角度来说线程和进程基本上没有什么区别。
1。什么是进程
首先,抽象来说,我们的计算机是这样的东西:
![]()
这个大矩形代表计算机的内存空间,小矩形代表❀,即左下角代表磁盘,右下角的图形代表一些输入输出设备,例如鼠标、键盘、显示器等。还要注意内存空间是分为两部分。上半部分代表用户空间,下半部分代表内核空间。
用户空间存储用户进程需要使用的资源。例如,如果您在程序代码中打开一个数组,则该数组必须存在于用户空间中;内核空间存放内核进程需要加载的系统资源。这些资源通常不允许用户使用。的。但需要注意的是,有些用户进程会共享一些内核空间资源,比如一些动态链接库等。
我们正在用C语言编写一个hello程序。编译后我们得到一个可执行文件。当我们在命令行上运行它时,我们可以打印短语“hello world”,然后程序就会退出。在操作系统级别,创建一个新进程。这个进程读取我们编译到内存中的可执行文件,运行它,最后退出。
你编译的可执行文件只是一个文件,而不是一个进程。可执行文件必须先加载到内存中并包装到进程中,然后才能实际运行。进程是由操作系统创建的。每个进程都有其固有的属性,如进程号(PID)、进程状态、打开的文件等。进程创建后,读取程序,该程序就会被系统执行。
那么,操作系统是如何创建进程的呢? 对于操作系统来说,进程就是一种数据结构。我们直接看Linux源码:
struct task_struct {
// 进程状态
long state;
// 虚拟内存结构体
struct mm_struct *mm;
// 进程号
pid_t pid;
// 指向父进程的指针
struct task_struct *parent;
// 子进程列表
struct list_head children;
// 存放文件系统信息的指针
struct fs_struct *fs;
// 一个数组,包含该进程打开的文件指针
struct files_struct *files;
};
task_struct是Linux内核对进程的描述,也可以称为“进程描述”。源代码相当复杂,所以我在这里捕获了一小部分更常见的代码。
我们主要讨论mm指针和文件指针。mm指向进程的虚拟内存,这是加载资源和可执行文件的地方; files指针指向一个包含该进程打开的所有文件的数组。指针。
2.什么是文件描述?
首先我们来谈谈文件。它是一个文件指针数组。一般来说,进程将从 files[0] 读取输入,将输出写入 files[1] 并将错误消息写入 ] 文件。
比如,从我们的角度来看,C语言中的 每个进程创建时, 我们可以重画一张图: 对于一般的计算机来说,输入流是键盘,输出流是屏幕,错误流也是屏幕,所以现在这个过程有3条线连接到核心。因为硬件是由内核管理的,所以我们的进程必须使用“系统调用”来让内核进程访问硬件资源。 PS:别忘了Linux中的一切都被抽象为文件,设备也是可以读写的文件。 如果我们正在编写的程序需要其他资源,比如打开一个文件进行读写,这也很容易。进行系统调用并让内核打开该文件。文件会放在 明白这个原理,输入重定向就很容易理解了。当程序要读取数据时,就会去 同样,输出重定向是将 错误重定向 是一样的,就不赘述了。 管道字符实际上具有相同的目的。它将一个进程的输出流和另一进程的输入流连接成一条“管道”,并在其中传输数据。不得不说,这个设计思想真是巧妙: 此时,你也能看出“Linux中的一切都是文件”的巧妙设计思想了。无论是设备、另一个进程、套接字还是真实的文件,一切都可以读写,并且可以顺利加载。一个简单的 首先要明确,多进程和多线程都是并发的,都可以提高处理器的利用效率。那么现在的关键是,多线程和多进程有什么区别。 为什么Linux中线程和进程基本没有区别?因为从Linux内核的角度来看,线程和进程并没有区别对待。 我们知道系统调用 换句话说,线程看起来和进程没有什么不同,只不过线程中的某些数据区域是与其父进程共享的,而子进程则进行复制,而不是共享。例如,结构 所以,我们的多线程程序必须使用加锁机制来防止多个线程同时向同一个区域写入数据,否则可能会导致数据混乱。 那么你可能会问,既然进程和线程是一样的,而且多进程数据不共享,也就是不存在数据混乱的问题,为什么多线程的使用会普遍得多比多进程? ? 因为同时数据共享在现实中更为常见。比如十个人同时从一个账户提取十块钱。我们想要的是这个共享账户的余额正确减少一百元,而不是希望每个人都得到一份账户的副本。 ,每个跟单账号减十元。 当然,必须注意的是只有Linux系统将线程视为共享数据的进程,并没有对其进行特殊对待。许多其他操作系统以不同的方式对待线程和进程。是的,线程有自己独特的数据结构。我个人认为它并不像Linux的设计那么简单,这增加了系统的复杂性。 Linux中创建新线程和进程的效率非常高。关于创建新进程时复制内存区域的问题,Linux采用了copy-on-write策略优化,这意味着父进程的内存并没有被真正复制。空格,但要等到需要进行写操作才能复制。 因此在 Linux 中创建新进程和线程非常快。 作者:labuladongprintf函数将字符打印到命令行,但从流程的角度来看,它是写入到files[1]输入;同样,scanf函数是尝试从文件files[0]读取数据的过程。 files中的前三位填充为默认值,分别指向标准输入流、标准输出流和标准错误流。我们常说的“文件描述符”就是指这个文件指针数组的索引,所以默认情况下程序的文件描述符是0表示输入,1表示输出,2表示错误。 ![]()
files 第四个位置对应文件描述 3: ![]()
files[0]读取,所以我们只要将files[0]指向一个文件,程序就会读取数据从这个文件而不是从键盘:![]()
files[1]指向一个文件,那么该文件将不会输出到文件中。屏幕,但到这个文件: ![]()
![]()
files数组,进程通过简单的文件描述符访问对应的资源,具体的细节交给操作系统,有效解耦,美观高效。 3.什么是线程
fork()可以创建新的子进程,函数pthread()可以创建新的线程。 但是线程和进程都由结构体task_struct表示。唯一的区别是共享数据区域不同。 mm 和 文件 在线程之间共享。我画两张图你就明白了:![]()
![]()
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网