进程概念
进程是什么?
简单的来说进程就是运行中的程序。而这样理解显然是不够的。
从操作系统的层面理解:
当一个程序运行起来需要将代码数据加载到内存中,操作系统中运行了很多程序,这时候操作系统就需要把这些程序先描述,再组织进行管理。对于操作系统来说,进程就像对一个运行的程序的描述。在操作系统眼里,看到进程就相当于看到了一个程序,而这个对程序的描述信息就是 — PCB(进程控制块)。
从内核观点来看:进程就是担当分配系统资源(CPU时间,内存)的实体。
Linux下的PCB是一个名为task_struct的结构体,它被装载在内存中并且包含着进程的描述信息,所以可以说Linux下进程就是一PCB。
关于CPU分时机制:进程在操作系统中是调度切换运行,每个进程都有一个CPU时间片,在CPU时间片运行完毕之后则切换下一个进程。
PCB中的描述信息
PCB中包含很多进程的描述信息,常用的有:进程标识符(PID)、进程状态、优先级、程序计数器、内存指针、上下文数据、IO状态信息、记账信息等等。
操作系统通过描述信息中的内存指针能找到内存中运行的程序代码以及数据,并且通过上下文数据可以曹村程序调度切换时正在处理的数据,在下次调用的时候加载进寄存器中继续处理,以及可以通过程序计数器保存进程切换时进程即将执行的下一步指令,在下次调度的时候就从上次运行的下一步开始继续执行。IO状态信息中保存进程的IO请求,分配个进程的IO设备以及文件列表,记账信息主要保存处理机时间综合,时间限制等其他信息。
如何查看进程
可以通过ps命令,也可以在 /proc 文件下查看。
ps -aux
ps -ef //没上面的查看的详细
pid_t pid = getpid(); //获取调用进程的PID
进程状态
大体的看进程有三种状态分别是:就绪状态,运行状态,阻塞状态。Linux下进程的详细状态有:
- 运行状态 R:正在运行,或者在运行队列中等待时间片的进程;
- 可中断休眠状态 S:能够被简单的操作唤醒,通常是进程在等待事件的完成。
- 不可中断休眠状态D:通常只能被特定的方式唤醒,通常是进程在等待 IO 结束。
- 停止状态 T:用kill杀不死这个进程,可以通过SIGSTOP信号来停止进程,用SIGCONT信号让进程继续运行。
- 死亡状态 X:这个状态只是一个返回状态,不会在任务列表中看到这个状态。
除此之外,还有两个特殊的状态:僵尸进程和孤儿进程。
僵尸进程 处于僵死状态 Z,杀不掉的进程,处于僵死状态的进程,通常是进程退出了,但是资源没有完全释放。
僵尸进程的产生原因:
子进程退出了,因为要给父进程返回退出原因,因此操作系统并不能完全释放子进程的所有资源,但是父进程并没有关注这个通知,导致子进程的退出原因没有被接收,操作系统不被允许释放这块资源,这个时候子进程处于僵死状态。简单来说就是子进程退出了,但是没有完全退出。这样造成的危害就是内存泄漏。
处理方式:退出父进程,父进程退出之后子进程的退出原因就没有保存的意义了,子进程已将被释放干净。
避免僵尸进程产生的方法通常的进程等待的方式。
孤儿进程父进程先于子进程退出,子进程成为孤儿进程,运行在后台,该子进程的父进程变为 1 号进程,孤儿进程退出之后,1号进程将释放孤儿进程的所有资源。
守护进程(精灵进程):特殊的孤儿进程,通常执行特殊任务。比如:脱离终端、会话的影响,执行某个服务,运行在后台。
环境变量
环境变量就是存储系统运行环境参数的变量,作用是让系统的参数设置变得简单。例如:在C语言程序中,链接的时候,从来不知道我们所链接的动态或静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
Linux下关于环境变量的一些命令:
查看环境变量:set、env、echo (set查看的不仅是环境变量,还有用户自己定义的变量等等)
设置环境变量 :export
删除环境变量:unset
环境变量的特性:环境变量具有全局特性,可以被子进程继承下去。父进程设置了子进程的环境变量。
环境变量在代码中的获取:
- 通过环境变量名称获取
char* getenv(char* name) //接口
char* ptr = getenv(PWD) //示例
- main函数的第三个参数保存了环境变量,以NULL结尾
int main(int argc, char* argv[], char* envp[])
//argc表示命令行参数的个数,第一个就是执行程序名,所以argc最少是1
// argv[] 保存具体的参数
// envp[] 保存环境变量
- C标准库下的全局变量 environ
extren char** environ;
//extren 通知编译器要用这个东西
环境变量的使用场景:通常是父进程通过给子进程设置环境变量,来达到向子进程传递数据的功能。
程序地址空间
每个进程都被操作系统分配了虚拟地址空间,由3G用户空间(代码段,数据段,堆,共享区,栈),1G内核空间组成。
本质上是操作系统位进程进行的虚拟的内存描述,是一个mm_struct, 就是内存指针,保存在PCB中。
struct mm_struct{
ulong mem_size;
ulong code_start;
ulong code_end;
.....
}
分页内存访问
每个进程都有一个页表,父子进程中,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被页表映射到了不同的物理地址。
为什么要使用虚拟地址空间,有什么用?
进程先访问虚拟地址进而获取变量数据,最终还是要去访问物理内存,因为数据是存储在物理内存中的。在虚拟地址和物理地址之间通过页表进行地址映射,转换得到物理地址,进而访问物理内存。经过映射之后的物理地址不一定是连续的,通过地址映射转换的方式实现数据的离散存储,提高内存的利用率。同时页表中记录了这块地址属性,可以实现内存访问控制,保持进程独立性。
进程之间的代码共享和数据独有
代码共享:
操作系统通过复制父进程(PCB)创建子进程,子进程初始时和父进程指向同一块物理内存(因为复制PCB所以内存指针等信息完全相同),所以父子进程在虚拟地址空间中内存指针指向同一代码段,并且上下文数据,程序计数器等完全相同,因为就是两个相同的PCB。
数据独有:
父子进程,输出地址是一致的,但是变量内容不一样!这是因为我们输出的地址是虚拟内存地址,因为创建子进程的时候,我们拷贝了父进程的PCB,所以两进程程序地址空间的内容完全相同,父子进程的数据通过页表映射在同一片空间。但是当子进程的数据发生改变的时候,子进程相关数据会重新开辟空间,并且页表的相关映射关系也会发生改变,这样两个进程的数据地址相同,其实映射的却是不同的物理内存空间。