一:冯诺依曼体系结构
什么叫做体系结构???
计算机组成 / 芯片架构
输入单元:键盘、话筒、摄像头、usb、鼠标、磁盘(ROM)/ssd、网卡、显卡
存储器:内存(RAM)
中央处理器(CPU):寄存器、各种级别的缓存(cache)
输出单元:显示器、喇叭、打印机、磁盘、网卡、显卡
计算机里面的几乎所有的设备,都有数据存储的能力!!!
CPU这个设备,他的数据处理速度是非常快的,然后是内存,然后是各种外设(磁盘)。
程序在运行之前,必须先加载到内存?为什莫?
程序 = 代码 + 数据
最终都要CPU来执行处理,CPU需要先读取到这些代码和数据,而CPU只和内存有“数据(二进制层面)”层面的交互,但是形成的 exe,本质就是一个文件,只能在磁盘(外设)中保存。所以必须要在程序运行之前先加载到内存中。
二:操作系统
1.什么是操作系统?
操作系统(Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。
操作系统是用户和计算机的接口,同时也是计算机硬件和其他软件的接口。操作系统的功能包括管理计算机系统的硬件、软件及数据资源,控制程序运行,改善人机界面,为其它应用软件提供支持等,使计算机系统所有资源最大限度地发挥作用,提供了各种形式的用户界面,使用户有一个好的工作环境,为其它软件的开发提供必要的服务和相应的接口。
操作系统是一款软件,进行软硬件资源管理的软件。
2.为什么要有操作系统?
操作系统将软硬件资源管理好(手段),
给用户提供良好的(稳定,高效,安全)使用环境(目的)。
如何管理呢???
什么是真正的管理者?(做决策/做执行)做决策,如何做正确的决策 ---> 根据完善的数据
示例:在学校中,管理者是校长,但与学生接触最多的是辅导员。
校长(做决策) 辅导员(做执行) 学生(被管理)
校长为了更方便的管理学生,可以制作一个execl表格,将学生的各项信息填入表格中,当数据发生变动时,在表格中进行修改,但这种方法很不方便 ---> 创建结构体,存放学生的各项信息,并将其各个节点连接起来组成链表,校长就可以直接对链表进行增删查改,不需要每天盯着表格操作。 管理的本质不是管人,而是管理数据。上述的描述,本质上就是一个建模的过程。
先描述,再组织 任何管理工作都可以经过这六个字进行计算机建模!!!
下图是,操作系统对软硬件:
操作系统并不直接和硬件打交道,那么如何将其管理好?是通过(硬件相对的)数据管理的,通过驱动程序拿到数据。
操作系统管理:核心是:进程管理、内存管理、文件/IO管理、驱动管理。
3.系统调用和库函数的概念
系统调用:操作系统不相信任何用户,但又不得不向我们提供服务,在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口
库函数:系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
三:进程
1.基本概念
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。
进程 = 可执行程序 + 内核数据结构(PCB)
2.描述进程
<1>: 如何描述进程---PCB
进程信息被放在一个叫做进程控制的块的数据结构中,可以理解为进程属性的集合。
这个数据结构叫做PCB --- Linux操作系统下的PCB是 task_struct。
<2>:task_struct 内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
<3>:组织进程
3.通过系统调用获取进程标识符
进程 id (PID)
父进程 id (PPID)
示例:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("pid: %d\n",getpid());
printf("ppid: %d\n",getppid());
return 0;
}
运行结果为:
在命令行中,父进程一般为命令行解释器(bash)。
4.通过系统调用创建进程 --- fork 初识
fork 函数:
返回值:有两个返回值,一个返回父进程的子进程的 id,还有一个是子进程的 id (子进程 id 返回0说明,子进程创建成功,反之不成功)。
示例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
printf("pid:%d ,ppid: %d\n",getpid(),getppid());
int ret = fork();
printf("pid: %d ,ppid: %d ,ret: %d\n",getpid(),getppid(),ret);
sleep(1);
return 0;
}
代码运行结果为:
我们发现,一个 printf( ) 函数打印了两次,而且两次打印的 ret 值不同,为什么会出现 这种现象?
只有父进程执行 fork 之前的代码,fork 之后,父子进程都要执行后续的代码,即 printf 函数既要被父进程执行一次,又要被子进程执行一次,所以在此处,会出现打印两次的情况。
<1>:为什么 fork 之后会有两个返回值?
在 fork()函数体内,函数返回 id 前已经完成了子进程的创建,既然完成了子进程的创建,那么子进程就也会去到运行队列中,等待CPU调度,父子进程共享代码,数据各自开空间。由于返回值id是数据,所以虽然id的变量名相同,但是内存地址不同,所以返回的id是不同的。
<2>:fork 两个返回值,为什么给父进程返回子进程的 pid ,给子进程返回0?
父进程返回子进程 id , 父进程可以直接找到子进程,子进程返回 0 表示创建进程成功。
<3>:fork 之后,父子进程的运行顺序是什么样的?
创建完成子进程,只是一个开始,创建完成子进程之后,系统的其他进程,父进程和子进程,接下来都要被调度执行,当父子进程的PCB都被创建并在队列中排队的时候,哪一个进程的PCB先被选择调度,那个进程就先运行!即父子进程运行的顺序是不确定的,它由操作系统自主决定,由各自的PCB中的调度信息(时间片、优先级等)+ 调度器的算法共同决定。
fork() 之后一般要 if 分流:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("我是子进程,pid:%d ,ppid: %d\n",getpid(),getppid());
}
else
{
printf("我是父进程,pid:%d ,ppid: %d\n",getpid(),getppid());
}
sleep(1);
return 0;
}
代码运行结果为:
四:进程状态
- 运行状态(R) 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- 睡眠状态(S)意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠。
- 磁盘休眠状态(D) 有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- 停止状态(T)可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- 死亡状态(X)这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
1.进程状态的查看
查看进程状态的命令:
ps aux / ps axj
运行结果为:
2.僵尸进程
概念:进程退出了,但是还没有被父进程/操作系统(OS)读取,操作系统(OS)必须维护这个退出进程的PCB结构,此时,该进程的状态为僵尸状态(Z)。等到父进程或者操作系统(OS)读取之后,PCB的状态先被改成死亡状态(X)状态,才会被释放。
如果一个僵尸进程的父进程没有回收该进程,那么该进程的PCB将一直存在,如果我们不及时回收,会存在内存泄露的风险。
示例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int i = 5;
while(i)
{
printf("我是子进程,pid:%d ,ppid: %d ,i: %d\n",getpid(),getppid(),i);
sleep(1);
i--;
}
}
else
{
while(1)
{
printf("我是父进程,pid:%d ,ppid: %d\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}
在此处我们使用以下内容检测进程的运行状态:
while :;do ps aux | grep mycode | grep -v grep;sleep 1;echo "---------------------";done
运行结果为:
3.孤儿进程
概念:子进程的父进程先退出,此时子进程就称为孤儿进程,孤儿进程要被1号init进程领养,由init进行回收。
示例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("我是子进程,pid:%d ,ppid: %d\n",getpid(),getppid());
sleep(10);
}
else
{
printf("我是父进程,pid:%d ,ppid: %d\n",getpid(),getppid());
sleep(3);
exit(0);
}
return 0;
}
运行结果为:
4.进程优先级
基本概念:
- cpu资源分配的先后顺序,就是进程的优先权。
- 优先权高的进程有优先执行的权利,配置进程优先权对多任务环境的linux很实用,可以改善操作系统性能。
- 还可以把进程运行在指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统的整体性能。
查看系统进程:
top
运行结果为:
通过上述图片,我们可以从中看到以下几个内容:
UID:代表执行者的身份
PID:代表这个进程的信号
PPID:代表这个进程是由那个进程发展衍生而来的,既父进程
PRI:代表这个进程可被执行的优先级,值越小越先被执行
NI:代表这个进程的 nice 值
Linux进程的优先级数值范围为 60-99 (40个)
Linux中默认进程的优先级为 80
Linux支持动态优先级调整,那么如何调整?
nice值:进程优先级的修正数据
pri(新) = pri(旧) + nice 且 pri(旧) =80
进入top后按“r”–>输入进程PID–>输入 nice 值
nice 调整的最小值为-20,nice 调整的最大值为19。
nice 值超出最大最小调整范围时,超过部分按最大/最小计算。
为什么要把优先级限定在一定的范围内???
操作系统(OS)调度的时候,较为均衡的让每一个进程都要得到调度,这样比较公平。
如果不加以限制,可能会导致优先级较低的进程,长时间得不到CPU资源 --- 进程饥饿。
5. 其他概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。