冯诺依曼体系结构:输入设备,输出设备,存储器,运算器,控制器
进程的相关概念:
竞争性(系统进程数目众多但CPU资源只有少量,所以进程之间具有竞争性)
独立性(多进程运行,各自独享资源互不干扰)
并行(多个CPU在多个CPU下分别同时进行)
并发(多个进程一个CPU用进程切换方式运行)
环境变量:用于设置系统运行环境的变量(使程序运行更加高效,因为环境变量具有全局特性)
查看环境变量:env(只有环境变量) set(不仅仅有环境变量) echo(直接指定一个环境变量的名称进行查看)
声明环境变量:export
删除环境变量:unset
获取一个环境变量:getenv()
获取全部环境变量:
1.main函数第三个参数法 int main(int argc,char *argv[ ],char *envri[ ]){ int i; for(i=0;envri[i] != NULL;i++{ printf("env[%d]:[%s]",i,envri[ i ]; } }
2.全局变量 char **environ;(这个是在库里定义的) int i; for(i=0;envri[i] != NULL;i++{ printf("environ[%d]:[%s]",i,environ[ i ]; }
程序地址空间:
进程地址空间:4G,其中1G内核空间,3G用户空间
进程的虚拟地址空间:我们所说的程序地址空间,实际上是一个进程的虚拟地址空间,目的是为了告诉进程,每个进程都有一个完整的连续的内存让程序能够运行,但是真正一个进程使用的内存经过页表映射之后可能只使用了很少一部分物理内存
页表:记录虚拟地址与物理地址的映射关系,并且对内存进行访问控制(地址的前20位是页表的页号,后12位是页内偏移)
分段式内存管理:将内存分成段,每个段的起始地址就是段起址,然后用连续的地址空间记录内存。地址映射的时候,用段起址加上段长表示实际内存中的内存。
打个比方就是记笔记,先把语文笔记全抄完,然后把数学笔记全抄完,再把英语笔记全抄完,最后用一张纸(段表)记录从哪开始(段起址)到哪里(段长)是语文笔记,哪到哪是数学笔记,哪到哪是英语笔记
在段式存储管理中,将程序的地址空间划分为若干段,如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。
优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)
缺点是:段换入换出时,产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)
分页式内存管理:内存分成固定长度的一个个页片。地址映射的时候,需要先建立页表,页表中的每一项都记录了这个页的基地址。通过页表,由逻辑地址的高位部分先找到逻辑地址对应的页基地址,再由页基地址偏移一定长度就得到最后的物理地址,偏移的长度由逻辑地址的低位部分决定。一般情况下,这个过程都可以由硬件完成,所以效率还是比较高的。
打个比方就是一本书,把物理内存分成一页一页的形式,就像一本书一样组成了内存,然后页表就好比如一个索引,为你找到每一页虚拟内存对应的物理内存
在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的页框,程序加载时,可以将任意一页放入内存中任意一个页框,这些页框不必连续,从而实现了离散分离。
优点是:没有外碎片(因为页的大小固定)
缺点是:会产生内碎片(一个页可能填充不满)
若给定一个逻辑地址为A,页面大小为L,则页号P=INT[A/L],页内地址W=A % L
分段式和分页式内存管理的不同:
(1) 分页仅仅是由于系统管理的需要而不是用户的需要。段则是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好地满足用户的需要。 (2) 页的大小固定且由系统决定,由系统把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而在系统中只能有一种大小的页面;而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分。 (3) 分页的作业地址空间是一维的,即单一的线性地址空间,程序员只需利用一个记忆符,即可表示一个地址;而分段的作业地址空间则是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。
段页式内存管理:地址映射的时候,先确定对应的段号,确定段起址;段内分页,再找到对应的页表项,确定页起址;再由逻辑地址低位确定的页偏移量,就能找到最终的物理地址。
如何通过虚拟地址找到对应的物理地址:先通过页表将虚拟地址的页号转换成物理地址的页号,然后将虚拟地址的偏移号拷贝一份给物理地址,和物理地址的页号拼接后就得到了实际的物理地址
详细过程:
1.程序执行时,从PCB中取出段表始址和段表长度,装入段表寄存器。
2.由地址变换机构将逻辑地址自动分成段号、页号和页内地址。
3.将段号与段表长度进行比较,若段号大于或等于段表长度,则表示本次访问的地址已超越进程的地址空间,产生越界中断。
4.将段表始址与段号和段表项长度的乘积相加,便得到该段表项在段表中的位置。
5.取出段描述子得到该段的页表始址和页表长度。
6.将页号与页表长度进行比较,若页号大于或等于页表长度,则表示本次访问的地址已超越进程的地址空间,产生越界中断。
7.将页表始址与页号和页表项长度的乘积相加,便得到该页表项在页表中的位置。
8.取出页描述子得到该页的物理块号。
9.对该页的存取控制进行检查。
10.将物理块号送入物理地址寄存器中,再将有效地址寄存器中的页内地址直接送入物理地址寄存器的块内地址字段中,拼接得到实际的物理
地址。
写时拷贝技术:提高创建子进程效率(先让子进程和父进程指向同一块区域,共用一块内存,等内存的某块区域发生改变再把改变的地方单独开辟一块空间,然后拷贝给子进程,这就是为什么代码共享数据独有)
MMU:内存管理单元
进程调度:cpu调度进程,实际上调度的是pcb(空间换时间)
大O(1)调度算法:用队列进行嵌套,先把pcb入队,然后把每个队列再嵌进另一个队列数组中,然后用PRI进行标记(这也解释了为什么PRI越小,优先级越高,因为PRI越小下标越小,越早出队),但还有一个队列中是否有结点(在这里指的是pcb)的问题,这里采取了位图进行标记,如果入队则为做一个标记。当队列取完后,为了提升效率,牺牲空间把指针调换再创建一个同样大的嵌套队列空间,上一个队列取一个,下面就放一个
进程概念:
什么是进程:进行中的程序---抽象化的概念(站在用户角度)
进程是一个运行中的程序,在操作系统中,一个程序运行起来,程序被加载到内存中,操作系统创建了一个进程描述符(进程控制块)PCB,PCB对程序的运行进行描述控制,因此进程就是pcb,pcb在Linux下的task_struct结构体中
内存指针:pcb中的指针,指向进程运行起来后程序的代码和数据在内存中的位置
标识符pid(父进程标识符是ppid)
文件状态信息:
记账信息:进程在cpu上运行的时间,用于判断cpu是否处于一个饥饿状态
上下文数据:防止切换调度时丢失数据
程序计数器:同上
进程状态 :
通用说法:阻塞,运行,就绪
Linux进程状态转换图
具体信号传递:
Linux下的进程状态:
R可执行状态(TASK_RUNNING):正在运行或准备运行(具体分为用户运行态,内核运行态和就绪态)
S可中断睡眠(TASK_INTERRUPTIBLE):正在休眠,但是可以被唤醒
D不可中断睡眠(TASK_UNINTERRUPTIBLE):正在休眠,不可唤醒,如果该进程中断会出现异常
T暂停状态(TASK_STOPPED):进程已停止,但是可以通过其它进程恢复(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行,收到SIGCNT信号后进程继续运行)
T追踪(了解即可):正在被调试的进程
Z僵尸状态(TASK_ZOMBIE):进程除PID外,其它资源全部释放
X退出(了解即可):进程完全退出,连PID都没了,这一状态进程中是看不到的
进程状态后缀:
< 表示高优先级 n 表示低优先级 s 包含子进程 + 位于后台的进程组
僵尸进程:处于僵尸状态的进程
危害:资源泄漏
产生:子进程先于父进程退出,操作系统通知父进程,但是父进程没有联系上,然而操作
系统擅自释放子进程资源(一旦释放就没地方保存退出原因,因此子进程就成了僵尸进程
处理:退出父进程
预防:进程等待
孤儿进程(操作系统没有这个定义,这是人们定义的):父进程先于子进程退出,子进程就会变成孤儿进程,孤儿进程的父进程将变为1号init进程,并且孤儿进程退出不会产生僵尸进程
守护进程(也叫精灵进程):特殊的孤儿进程
进程优先级:决定CPU资源的优先分配权
查看进程: /proc 这个目录保存当前运行的进程信息
ps命令:
ps -l(查看当前登陆的PID以及相关信息)
F 代表这个程序的旗标 (flag), 4 代表使用者为 superuser;
S 代表这个程序的状态 (STAT);
UID 代表执行者身份
PID 进程的ID号
PPID 父进程的ID;
C CPU使用的资源百分比
PRI指进程的执行优先权(Priority的简写),其值越小越早被执行;(PRI=PRI+NI)
NI 这个进程的nice值,其表示进程可被执行的优先级的修正数值。(修改优先级的法:用nice和renice改变NI值)
ADDR 这个是内核函数,指出该程序在内存的那个部分。如果是个执行 的程序,一般就是『 - 』
SZ 使用掉的内存大小;
WCHAN 目前这个程序是否正在运作当中,若为 - 表示正在运作;
TTY 登入者的终端机位置;
TIME 使用掉的 CPU 时间。
CMD 所下达的指令名称
ps -aux(查看当前所有正在内存中的程序)
ps -ef (也是查看当前所有内存中的程序,只是展示的风格不同)
USER:该进程属于那个使用者账号。
PID :该进程的进程ID号。
%CPU:该进程使用掉的 CPU 资源百分比;
%MEM:该进程所占用的物理内存百分比;
VSZ :该进程使用掉的虚拟内存量 (Kbytes)
RSS :该进程占用的固定的内存量 (Kbytes)
TTY :该进程是在那个终端机上面运作,若与终端机无关,则显示 ?。另外, tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等
的,则表示为由网络连接进主机的程序。
STAT:该程序目前的状态,主要的状态有:
R :该程序目前正在运作,或者是可被运作;
S :该程序目前正在睡眠当中,但可被某些讯号(signal) 唤醒。
T :该程序目前正在侦测或者是停止了;
Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸) 程序的状态
START:该进程被触发启动的时间;
TIME :该进程实际使用 CPU 运作的时间。
COMMAND:该程序的实际指令。
top命令(查看CPU占用率高的前几个进程信息)
在程序中操作: 获取进程pid() 获取进程标识符:getpid()
创建进程: 实质上就是创建一个pcb
pid_t fork(void):通过复制一个进程创建一个新的进程,创建一个新的pcb(进程),并且复制父进程pcb中的数据,因为复制了父进程的pcb,意味着子进程和父进程代码和数据指向同一块内存区域,所以父子进程代码共享,但是数据独有
返回值: 1.对于父进程来说,返回创建的子进程的pid 2.对于子进程来说,返回值是0 3.失败返回-1
我们用户就是通过返回值对父子进程进行代码分流
pid _t vfork(void):也是创建一个子进程,子进程和父进程共享数据段
返回值:1.对于父进程来说,返回创建的子进程的pid 2.对于子进程来说,返回值是0 3.失败返回-1
fork和vfork的区别:
1. fork ():子进程拷贝父进程的数据段,代码段
vfork ():子进程与父进程共享数据段
2. fork ():父子进程的执行次序不确定
vfork ():保证子进程先运行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec或exit 之后父进程才可能被调度运行(如果子进程没有调用 exec, exit, 程序则会导致死锁,程序是有问题的程序,没有意义)
修改进程状态:
kill命令
格式:kill [选项] 参数
l:列出全部的信号名称
a:处理当前进程时,不限制命令名和进程号的对应关系
p:指定kill命令只打印相关进程的进程号,不发送任何信号
s:指定发送信号
u:指定用户
kill-l 查看系统支持的信号列表
kill-SIGSTOP 通过发送SIGSTOP信号给进程来停止(T)进程
kill-SIGCONT 通过发送SIGCNT信号让进程继续运行。
kill()函数:用来将参数sig指定的信号传递给参数pid指定的进程
int kill(pid_t pid,int sig)
pid:
pid>0 将信号传给进程识别码为pid 的进程。
pid=0 将信号传给和目前进程相同进程组的所有进程
pid=-1 将信号广播传送给系统内所有的进程
pid<0 将信号传给进程组识别码为pid绝对值的所有进程
sig:进程信号,查表即可
返回值:成功返回 0 失败返回-1
CPU的分时计数:CPU只是在每个进程上运行很短的时间(时间片),如果CPU处理某个进程的时候时间片用尽,那么CPU就要去调度运行下一个程序了(一个进程并不是一直被CPU处理的,而是不断轮询切换CPU处理)