进程
进程是一个正在执行的程序,而程序就是程序员编写的代码,而这些代码被存放在硬盘中。
当这些程序和数据要被CPU执行时,根据冯·诺依曼体系结构规定,第一步就必须把它们加载到内存中。那么问题来了,CPU如何在内存中找到这些程序和数据呢?
PCB:进程控制块
在C语言中,通过指针找到数据的地址。同理,CPU需要通过某种介质来调度和运行,而这种介质就是PCB。
进程信息被放在一个叫做进程控制块的数据结构中(process control block),简称为PCB,实现对运行中程序的描述。CPU通过对PCB的调度管理实现对这些运行中的程序进行调度运行。
一句话,进程就是PCB。
在Linux下,这个PCB是一个结构体—struct task_ struct{…}
PCB的描述信息:
- 标识符:描述本进程的唯一标示符,用来区别其他进程
- 状态:任务状态,退出代码,退出信号等
- 优先级:相对于其他进程的优先级。
- 程序计数器:用于保存当前的程序运行,程序中即将被执行的下一条指令的地址
- 上下文数据: 进程执行时处理器的寄存器中的数据
- 内存指针:用于指向程序指令数据在内存中的位置,包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息
创建进程
pid_t fork(void) – 通过复制调用进程(父进程)创建一个新的进程(子进程)
pid_t getpid(void) – 返回调用进程的pid–谁调用就返回谁的pid
查看进程
ps -ef / ps -aux
进程状态(在Linux下)
进程状态 | 说明 |
---|---|
运行状态R(running) | 它表明进程要么是在运行中要么在运行队列里 |
睡眠状态S(sleeping) | 意味着进程在等待事件完成 |
磁盘休眠状态D(Disk sleep) | 在这个状态的进程通常会等待IO的结束。 |
停止状态T(stopped) | 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。 |
死亡状态X(dead) | 这个状态只是一个返回状态,不会在任务列表里看到这个状态。 |
僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程.
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
危害:
- 可父进程如果一直不读取,那子进程就一直处于Z状态。
- Z状态一直不退出,PCB一直都要维护。
- 那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。
- 内存泄漏
处理方法:进程等待
孤儿进程
父进程如果先于进程退出,那么子进程就称之为"孤儿进程"。
这个孤儿进程的父进程成为了1号进程,并且这个孤儿进程运行在后台。
孤儿进程不会成为僵尸进程,因为1号进程随时关注子进程的退出
守护进程
守护进程是一种特殊的孤儿进程,父进程是1号进程,运行在后台 ,与终端以及登陆会话脱离关系,不再受影响。
通常是一种运行在系统后台的批处理程序(默默的做一些循环往复的事情),也称为精灵进程。
进程地址空间
一个全局变量,在子进程中修改后,打印100,
父进程中依然打印1。
数据不同,表示没用使用同一块空间(一块空间不可能存储两个数据),所以父子进程打印的数据不同但地址却相同,很明显,两者是矛盾的。
实际上,进程访问的地址都是虚拟地址,而程序地址空间实际上是一个进程的虚拟地址空间。
虚拟地址空间其实是一个结构体 – mm_struct {…}, 是对内存空间的描述,通过这个描述向进程虚拟出一个完整的,连续的,线性的内存空间,为了能够不让进程直接访问物理内存。
那么如何通过虚拟地址访问物理内存?
操作系统为进程创建mm_struct 虚拟地址空间的同时,也创建了一个页表用于映射虚拟地址与物理地址的关系。
进程使用虚拟地址空间,通过页表映射物理内存,实现进程中数据在物理内存上的离散式存储。
通过这种方式,提高了内存的利用率,在页表中可以直接针对某个地址设置这个地址的访问权限(这个地址是只读的),通过这种方式实现内存访问控制。