进程和线程
wanyakun 5/19/2021
一个应用程序启动后会在内存中创建一个执行副本,这就是进程。Linux 的内核是一个 Monolithic Kernel(宏内核),因此可以看作一个进程。也就是开机的时候,磁盘的内核镜像被导入内存作为一个执行副本,成为内核进程。
进程可以分成用户态进程和内核态进程两类。用户态进程通常是应用程序的副本,内核态进程就是内核本身的进程。如果用户态进程需要申请资源,比如内存,可以通过系统调用向内核申请。
那么用户态进程如果要执行程序,是否也要向内核申请呢?
程序在现代操作系统中并不是以进程为单位在执行,而是以一种轻量级进程(Light Weighted Process),也称作线程(Thread)的形式执行。
一个进程可以拥有多个线程。进程创建的时候,一般会有一个主线程随着进程创建而创建。
# 进程与线程的区别
- 一个程序至少有一个进程,一个进程至少有一个线程
- 线程的划分尺度小于进程,使得多线程程序的并发性更高
- 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率。
- 线程在执行过程中与进程还是有区别的,每个独立的进程有一个程序运行的入口,执行顺序和程序的出口。但是线程不能够独立运行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
# 形象描述
- 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
- 假定工厂的电力有限,一次只能供给一个车间使用,也就是说,一个车间开工的时候,其他车间必须停工。背后的含义就是,单个CPU一次只能运行一个任务
- 进程就好比工厂的车间,它代表CPU所能处理的单个任务。任意时刻,CPU总是运行一个进程,其他进程处于非运行状态。
- 一个车间里,可以有很多工人,他们协同完成一个任务。
- 线程就好比车间里的工人。一个进程可以包括多个线程。
- 车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享的空间。
- 可是,每个房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
- 一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫“互斥锁”(Mutual exclusion,缩写Mutex),防止多个线程同时读写某一块内存区域。
- 还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。就好比某些内存区域,只能供给固定数目的线程使用。
- 这时的解决办法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来的时候再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做“信号量”(semaphore),用来保证多个线程不会互相冲突。不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者代替前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
- 操作系统的设计,因此可以归纳三点:
- 以多进程形式,允许多个任务同时允许以多线程形式,允许单个任务分成不同的部分运行提供协调机制,
- 一方面防止进程之间和线程之间产生冲突,
- 另一方面允许进程之间和线程之间共享资源。
# 进程在内存中是如何分配的
进程在内存中的分配主要有五部分:
- 代码段:存放的时程序的源代码
- data段:存放的是已经初始化的全局变量、静态变量
- bss段:存放的是未初始化的全局变量、静态变量
- heap段:保存动态分配的内存地址,比如c通过malloc申请的地址,或者c++中通过new获取的地址
- stack段:用在c程序中alloc函数从栈空间获取内存,返回的是自动释放
malloc函数是从堆空间获取内存;calloc函数和malloc基本一致,不同的是calloc函数会将申请到的内存初始化为0;relloc函数是重新分配内存,可以改变申请到的内存空间。
低地址->text->data->bss->heap(堆)->unused->stack(栈)->env->高地址