冯 诺依曼体系结构
大多数的笔记本,服务器都会遵守冯诺依曼体系
1. 此处的存储器指的是内存。
2. 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)。
3. 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
4. 所有的设备都只能和内存打交道。
下图用来模拟在qq上与好友聊天时,数据的流动过程。
如果是传文件最开始的输入设备变为磁盘,最后的输出设备变为显示器。
操作系统的概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(os)。
笼统的理解,操作系统包括:
1. 内核(进程管理、内存管理、文件管理、驱动管理)。
2. 其他程序(例如函数库,shell程序等等)。
设计os的目的
对下:与硬件交互,管理所有软硬件资源。
对上:为用户程序(应用程序)提供一个良好的执行环境。
其核心功能是:管理
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求也比较高。所以部分系统调用进行适度封装即可形成库,有了库就很有利于更上层用户或者开发者进行二次开发。
进程
1. 基本概念
进程,它代表了正在运行的程序的实例,当一个程序正在执行时操作系统会为它创建一个进程。进程可以管理各种资源、如内存、文件、设备等。他们可以申请和释放资源,并且可以对资源进行保护和共享。通过进程管理,操作系统可以有效分配资源并且可以防止资源浪费。
2. PCB
进程信息被放在一个叫做进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。
Linux操作系统下的PCB是task_struct,用来描述进程的结构体。
利用类似双向链表的形式将各个进程控制块PCB联系起来,方便管理。
3. task_struct
1. 标识符:描述本进程唯一标识符,用来区别其他进程。
2. 状态:任务状态,退出代码,退出信号等。
3. 优先级:相对于其他进程的优先级。
4. 程序计数器:程序中即将被执行的下一条指令的地址。
5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
6. 上下文数据:进程执行时处理器的寄存器中的数据源。
进程中的代码不可能在很短时间运行完的,规定每个进程的时间片(单次运行的最长时间),用户感受到的多个进程同时运行,本质是CPU的快速切换,CPU只有一套寄存器,为了保护上下文,进程的这些临时数据被写入在PCB中,再来执行时恢复上下文。
7. I/O状态信息:包括显式的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
9. 其他信息
进程操作
1. 查看进程信息
进程的信息可以通过/proc系统文件夹(在根目录下)查看
上面的数字就是进程的PID,记录对应进程的信息。
要获取PID为1的进程信息,就查看/proc/1这个文件夹。
我们使用指令ps aux查看所有的进程信息。
通过系统调用获取进程标识符
- 进程id(PID)
- 父进程id(PPID)
通过以下程序来查看
#include<iostream>
#include<cstdio>
#include<sys/types.h>
#include<unistd.h>using namespace std;int main()
{while(1){ printf("pid: %d\n",getpid()); printf("ppid: %d\n",getppid());}return 0;
}
可以通过ctrl+c或者kill -9 进程的PID杀死相应的进程。
而且我们发现每次程序的pid不一样但是ppid是一样的(实际就是bash)
2. 创建进程
我们可以利用fork函数创建一个子进程。
代码如下
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{fork();while(1){printf("pid: %d\n",getpid()); printf("ppid: %d\n",getppid());sleep(1); }return 0;
}
上图用红框圈起来的为该进程的pid和ppid,用蓝框圈起来的为通过fork()函数创建的子进程的pid和ppid。fork新创建的pid的父进程就是这个进程(即第一个进程)。
fork有两个返回值
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
因为父子进程代码共享,而且fork函数在执行return语句时已经创建好了子进程,所以return语句会被父子进程创建两次,所以fork函数肯定有两个返回值
- 若子进程创建成功,则在父进程中返回子进程PID,在子进程中返回0.
- 若子进程创建失败,则在父进程中返回-1。
通过fork的返回值,让父子进程分别执行不同的操作,如下代码
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
int main()
{ int id= fork();if(id<0){perror("fork");//出错return 1;}else if(id==0){printf("子进程:%d!, id=%d\n",getpid(),id);}else{printf("父进程:%d!, id=%d\n",getppid(),id);}sleep(1); return 0;
}
进程之间具有独立性,即使一个进程中途异常退出,也不会影响其他进程。
进程状态
下面是在kernel源代码里定义的:
/*
*The task state array is a strange "bitmap" of
*reasons to sleep. Thus "running" is zero, and
*you can test for combinations of others with
*simple bit tests.
*/
static const char *const task_state_array[] = {"R (running)", /*0 */"S (sleeping)", /*1 */"D (disk sleep)", /*2 */"T (stopped)", /*4 */"t (tracing stop)", /*8 */"X (dead)", /*16 */"Z (zombie)", /*32 */
};
R 运行状态(running):并不意味着进程一定在运行中,它表明要么是在运行中要么在运行队列里。
S 睡眠状态(sleeping):意味着进程在等待事件完成(这里的谁卖你也叫做可中断睡眠)
D 磁盘休眠状态(Disk sleep):有时候也叫做不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T 停止状态(stopped):可以通过发送SIGSTOP信号来停止(T)进程。这个被暂停的状态可以通过发送SIGCONT信号继续运行。
t 追踪状态(tracing stop):表示进程的追踪状态,主要在使用调试器时。
X 死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
查看进程状态
指令:
ps aux / ps axj
a:显示一个终端所有进程。
x:显示没有控制终端的进程,例如后台运行的守护进程。
j:显示进程归属的进程组ID,会话ID、父进程ID,以及与作业控制相关的信息。
u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等。
运行状态R
运行状态是不一定占用CPU的,也并不意味着进程一定在运行中,一个进程处于R状态,它只是表明进程要么是在运行中要么是在运行队列里,可以同时存在多个处于R状态的进程
浅睡眠状态S
进程在等待事情完成时
比如一段程序里的sleep(1000);,执行sleep这个时候进程就属于浅度睡眠状态。
浅度睡眠状态可以用kill指令杀死对应进程。
深度睡眠状态D
有时候也叫不可中断睡眠(深度睡眠状态),通常会等待IO的结束。不可以被kill杀掉
比如对磁盘进行写入时,那么在磁盘进行写入期间,该进程就处于深度睡眠状态。是不会被杀的,等磁盘回复是否写入成功以做出相应回答。
停止状态T
可以通过发送SIGSTOP信号来停止进程,这个被暂停的进程通过发送SIGCONT信号继续运行。
追踪状态t
程序被debug,断点:进程被暂停了。
僵尸状态Z
这是一个比较特殊的状态,当一个进程退出并且父进程没有读取到子进程退出的代码,即子进程退出父进程还在运行并且没有读取子进程状态,子进程就会进入Z状态。
僵死进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
死亡状态X
当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也不会存在了,所以无法观察到死亡状态。
僵尸进程与孤儿进程
僵尸进程
什么是僵尸进程
子进程退出父进程还在运行,父进程没有读取子进程状态,此时子进程就是一个僵尸进程
僵尸进程的危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。如果父进程一直不读取,那子进程就一直处于Z状态了。
维护退出状态本身就是要用数据来维护,也属于进程的基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。
并且父进程创建了很多子进程且没有回收,会造成内存资源的浪费(即内存泄漏)。因为数据结构对象是要占用内存的。进程退出之后内存泄漏问题就不存在了。
孤儿进程
那父进程提前退出,子进程后退出并且进入Z状态之后要怎么处理呢?
父进程先退出,子进程就称之为"孤儿进程"。
孤儿进程会被1号init进程(操作系统)领养,也由init进程回收
被领养后孤儿进程就变成了后台进程了,不能被ctrl+c杀掉,可以被kill -9 进程id 杀掉
这篇就到这里啦(づ ̄3 ̄)づ╭❤~