一、体系结构(冯诺依曼体系结构)硬件上
CPU:运算器和控制器组成
输入设备:话筒、摄像头、键盘、鼠标、磁盘等
输出设备:声卡、显卡、磁盘、显示器等
存储器:内存 —— 掉电易失
设备是连接起来的:通过总线连接以达到设备之间数据流动,本质就是设备之间进行数据拷贝,拷贝的速率就是决定计算机烤鳗的重要指标。
存储器存在的意义1.为了缓冲输入和输出设备过慢和CPU运行速度过快的问题,2.可以让计算机保持较高的效率同时,造价较低,使大众能使用电脑,冯诺依曼体系通过存储器使效率问题变成了软件问题
计算机存储金字塔:距离CPU越近,造价越高、容量小、速度快;距离CPU越远,造价越低、容量大、速度低
Q:程序在执行之前,为什么必须先加载到内存里面?
A:因为程序是文件,储存在磁盘里面,需要CPU去执行,而CPU是只与内存进行直接交互
二、操作系统 软件上
计算机开机第一个加载的就是操作系统,操作系统是一款软硬件管理软件
计算机层状结构
硬件层:包含计算机中所有组件
驱动程序:硬件厂商为了能够更好地调用其硬件而编写或者是公开接口让其他厂商去编写的用于操作系统和硬件之间进行交互的程序
操作系统:负责管理软硬件资源,提供基础的操作环境,支持程序的运行
操作系统对硬件管理的本质:先使用结构体将硬件组织起来,再通过数据结构将硬件管理起来,对下进行软硬件进行管理,对上提供一个良好的运行环境,用户不能直接跨过操作系统对硬件进行操作,操作系统提供了各种用户操作接口用,例如库函数等于用于进行开发和操作
系统调用和库函数概念
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分 由操作系统提供的接口,叫做系统调用。 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统 调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发
三、进程的理解
概念
程序:二进制文件,存放在磁盘上,在冯诺依曼体系中,属于外设(输入输出设备)
进程:本质上讲,进程=系统内核数据结构+可执行程序
进程控制块:操作系统层面用于描述进程的一个类,在Linux下面,该类叫做task_struct,在内存里面,一般用链表来组织进程,PCB类一般包括标识符、优先级、状态、程序计数器、等一百多个属性
创建进程的过程
当启动应用程序,系统会在先将可执行程序加载到内存里面,再在内存里面创建一个PCB对象,用于存放进程的信息,操作系统根据进程的信息将进程组织起来
所以进程本质上是由可执行程序和管理可执行程序的对象组成。
四、task_struct内部信息
task_struct内部信息有很多,有一百多,其中包括以下:
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息。
1)标识符
用于唯一区分进程的数字,类似于进程的"身份证号"。
查看系统进程的方法
(1)ls /proc【/PID】 可以查看所有的或者是指定PID的进程信息。
LInux会将进程以PID命名的文件夹形式存放在/proc下面
ls /proc/[PID]
(2)top 查看所有进程信息
top
(3)ps axj配合grep 可查询指定可执行程序名称的进程
ps axj | head -1&& ps axj |grep 可执行程序名/PID
例如,这里有一个名为mybin的程序,一直在运行中,使用该命令查询到有两条信息,第一条就是目标进程,第二条是查询这个过程产生的进程,所以可以得到一个
获取进程的 PID和PPID
由于task_struct的数据属于系统内核,所以要获取PID和PPID必须通过系统的接口来获取(gertpid)
通过查询手册可以得知,系统调用接口手册在第二节
使用方法,包含头文件<unistd.h>(包含getpid函数),<sys/types.h>(包含pid_t类型)直接调用,pid_t是系统层面的一个数据类型,相当于整型。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
pid_t id = getpid();
pid_t pid = getppid();
printf("PID is %d\n",id);
printf("PPID is %d\n",pid);
return 0;
}
PID和PPID的关系
通过观察,可以发现,每次启动进程的PID都是变化的,而PPID是不变的。
通过对PPID的搜索,可以看到,PID为16100对应的进程是bash,说明mybin的父进程是bash。
2)工作目录
查看工作目录
可以通过ll /proc/PID打开进程目录
cwd就是当前工作目录
修改工作目录
使用函数 chdir(新路径)
举例:睡眠20s将工作目录修改,等20s后再去查看工作目录
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
printf("PID is %d\n",getpid());
sleep(20);
chdir("/home/yw");
sleep(20);
}
20s前
20s后
3)进程状态
Linux下面的状态
/*
* 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状态:运行状态
S状态:阻塞状态
D状态:深度睡眠状态,(不可中断睡眠)磁盘正在检索的过程中,也是阻塞状态的一种
T状态:暂停状态
t状态:等待控制信号(例如调试过程)
X状态:死亡状态,进程马上要被终止
Z状态:僵尸状态,进程已经死亡,但是死亡状态还要维持一段时间,这段时间叫做死亡僵尸状态
状态:
所谓的状态,就是task_struct里面的一个整型变量,状态决定了进程的后续动作
当进程加载到内存中,并不是一直在运行的,进程可能在等待资源
运行状态
每一个CPU都有一个运行队列,CPU要按照运行队列里排队的顺序来对进程依次运行,在Linux下,进程只要进入到运行队列中进行等待或者是在执行过程中就是运行状态。
阻塞状态
进程在等待软硬件资源,在其他设备的队列中排队,例如,代码里面需要输入的地方,在等待输入的过程中就是在等待键盘输入资源,此时就处于阻塞状态,此时进程不会进入到CPU的运行队列中。
挂起状态
在内存资源比较紧张无法支撑某个进程运行的时候,操作系统会将阻塞状态中的进程的代码数据暂时存放在磁盘中的一个区域,叫做swap分区,因为阻塞原因被暂存在内存里面的进程的状态就是阻塞挂起状态。
在将进程暂存到swap分区的过程,叫做换出,从进程从swap分区拿回来到内存的过程叫做换入,换出的时候只会把代码数据和可执行程序暂存带swap分区,不会暂存对象。
查看进程状态
此处循环中不写语句是因为防止CPU运行代码后进行休眠状态
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
printf("PID is %d\n",getpid());
while(1){
}
}
通过查询进程命令
ps axj |head -1 && ps axj|grep 进程名
此处R+中,R表示是运行状态,+表示是前台运行状态,不带+就是后台运行状态,只有前台程序才能直接被ctrl+c直接终止,后台进程必须通过kill杀死。
4)进程优先级
优先级是进程为了某种资源的,进程需要进行排队对资源进行访问,在排队时的先后顺序。
优先级出现的意义是对有限的资源进行合理的调度
Linux的默认优先级为80,优先级是可以被修改的,范围是[60,99],数字越小,优先级越高
可以在top任务管理器中修改优先级,其修改的其实是nice值,PRI=80+nice值,如果nice的范围过大,PRI会被设置为极值。
如果不对进程的优先级加以限制,则进程之间优先级差异过大,会导致正常的进程很难获得系统资源,该现象称为进程饥饿。