这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战
基本概念
- 在Linux中,进程就是一个数据结构
- 理解文件描述符,重定向,管道命令的底层工作原理,就可以从操作系统的角度理解Linux中的线程和进程是没有区别的
进程
计算机结构
- 计算机结构:
- 内存空间:
- 上半部表示用户空间
- 下半部表示内存空间
- 进程
- 磁盘
- 输入输出设备
- 内存空间:
- 用户空间: 装着用户进程需要使用的资源
- 内核空间: 存放内核进程需要加载的系统资源,这些资源一般是不允许用户访问的. 但是有的用户进程会共享一些内核空间的资源,比如一些动态链接库等
- 编译好的可执行程序只是一个文件. 不是进程,可执行文件必须要载入内存,包装成一个进程才能运行
- 进程: 是要依靠操作系统创建的,每个进程都有固有属性
- 进程号PID
- 进程状态
- 打开的文件
- 进程创建好后,读入程序,程序才会被系统执行
- 对于操作系统,进程就是一个数据结构:
struct task_struct {
// 进程状态
long state;
// 虚拟内存结构体
struct mm_struct **mm;
// 进程号
pid_t pid;
// 指向父结构的指针
struct task_struct _ _rcu *parent;
// 子进程列表
struct list_head children;
// 存放文件系统信息的指针
struct fs_struct *fs;
// 一个包含进程打开的文件指针的数组
struct files_struct *files;
}
- task_struct是Linux对于一个进程的描述,称为进程描述符
- **mm: 指向的是进程的虚拟内存,即载入资源和可执行文件的位置
- *files: 指向一个存放所有该进程打开的文件指针的数组
文件描述符
- *files:
- 文件指针数组
- 通常情况下,一个进程会从files[0] 读取输入,将输出写入files[1], 将错误信息写入files[2]
- 文件描述符:
- 每个进程被创建时 ,file的前三位被填入默认值,分别指向标准输入流,标准输出流,标准错误流
- 文件描述符就是指这个数组的索引
- 程序的文件描述符默认情况下0是输入 ,1是输出 ,2是错误
- 默认情况下,计算机的输入流是键盘,输出流是显示器,错误流也是显示器.进程通过系统调用让内核进程访问硬件资源.在Linux中,一切皆文件,设备也是文件,可以进行读和写. 比如当前进程如果需要调用其余资源,则需要进行系统调用,让内核将文件打开,这个文件会被放到files的第4个位置
- 输入重定向:
- 程序读取数据会从files[0] 中读取
- 只要将files[0] 指向一个文件,而不是键盘
- 程序就会从这个文件中读取数据,而不是键盘
$ command < file.txt
- 输出重定向:
- 程序会将数据写入到files[1] 中输出
- 只要将files[1] 指向一个文件,而不是显示器
- 程序就会将数据写入到这个文件中,而不会写入到显示器
$ command > file.txt
- 错误重定向:
- 程序会将数据写入到files[2] 中输出
- 只要将files[2] 指向一个文件,而不是显示器
- 程序就会将数据写入到这个文件中,而不会写入到显示器
$ command > file.txt
- 管道符: 将一个进程的输出流和另一个进程的输入流接起一条 [管道], 数据就在这条管道中进行传递
$ cmd1 | cmd2 | cmd3
- Linux中一切皆文件:
- 不管是设备,进程 ,socket套接字还是真正的文件,全部都可以读写,统一装进一个简单的files数组
- 进程通过简单的文件描述符访问相应的资源. 具体细节交于操作系统,能够实现有效解耦,优美高效
线程
- 多线程和多进程都是并发,都可以提高处理器的利用效率
- Linux中线程和进程基本没有区别: 从Linux内核角度来说,线程和进程没有区别对待
- 系统调用函数pthread() 可以新建一个线程. 系统调用fork() 可以新建一个子进程
- 无论是线程还是进程,都是用task_struct结构表示的,唯一的区别就是共享的数据区域不同
- 线程基本上和进程没有区别,只是线程的某些数据区域和父进程是共享的,子进程是拷贝数据,而不是共享
- 多线程程序要利用锁机制,避免多个线程同时同时向同一区域写入数据,否则会造成数据错乱
- 问题: 既然进程和线程基本没有区别,并且多进程数据不共享,就不会存在数据错乱的问题,为什么多线程的使用比多进程要更加普遍呢?
- 因为在现实场景中,数据共享的并发更加普遍
- 只有Linux系统将线程看作是共享数据的进程,不做特殊对待. 其余操作系统是对线程和进程区别对待的,线程和进程各自持有特殊的数据结构
- Linux中新建线程和进程的效率都是很高的:
- 对于新建进程时内存区域拷贝的问题
- Linux采用copy-on-write策略优化
- 即并不真正复制父进程的内存区域空间,而是等到写操作时才去复制
- 所以,在Linux中新建线程和进程都是很迅速的