并发:
并行:
,多个程序分别运行在不同的核上,互不影响。并行确实提高了计算机的效率。
操作系统两种
状态:
操作系统的指令划分:
特权级:
相当于内核态,
相当于用户态
区别:
为了安全。在
中,如果有些指令用错会使系统崩溃,所以用户程序是不可信的,无论程序员是否有意,都可能把系统弄崩溃。
分了内核态和用户态之后,操作系统对内核级别指令进行封装,然后为用户提供系统服务。用户进行系统调用后,操作系统进行一系列的检查验证,确保调用是安全的,在进行相应的操作。
通过三种方式:系统调用、异常、外围设备的中断。
在执行处于用户态的程序时,发生了不可预料的异常,这时当前运行的进程会切换到处理相关异常的程序中,也就转到了内核态,最常见的是缺页异常。
发出相应的中断信号,这时
会停止下一条要执行的指令而转去处理中断。如果
先前执行的指令是用户态程序的指令,也就完成了用户态到内核态的转化。
宏内核:除了最基本的进程、线程管理、内存管理之外,还把文件系统、驱动、网络等都集成在内核中。比如
。
可能使整个系统崩溃,将修改和维护的代价提高
微内核:内核中只有最基本的进程、线程管理、内存管理,文件系统、驱动、网络等由用户态守护进程实现。比如
。
系统调用由操作系统提供,运行在内核态,指运行在用户态的程序向操作系统请求更高特权级的服务。系统调用提供了用户态和内核态之间的接口。例如在程序中打开文件、向文件进行写操作都是系统调用。
进程是具有独立功能的程序在一个数据集合上运行的过程。进程是系统进行资源分配的单位,实现的操作系统的并发。
线程是比进程更小的能独立运行的单位,是
调度的基本单位,实现了进程内部的并发。线程成为了程序执行流的最小单位。
之外的所有资源,只要分配到
就可以开始运行。
,正处于运行状态。
归还给系统。
当有多个进程请求资源时,就会造成内存资源紧张,所以操作系统还存在一种挂起操作:将进程交换到外存去,使进程进入挂起状态。
。
。
环境、硬件上下文,并导入新进程的的
环境、硬件上下文。
调度的最小单位。
环境保存和新调度进程的
环境配置。线程切换时,只需要保存少量内容,所以进程切换的系统开销更大。
进程可以提高系统的并发性和资源的利用率,但还是存在一些缺点的:
无法同时完成传输文件、视频聊天。
为了解决这些缺点,所以引入了线程作为进程内并发执行的更小单元,从而减少程序在并发执行过程中所付出的时空开销。
各个进程拥有自己独立的内存空间,为了保证安全,一个进程不可以直接访问另一个进程的内存空间。但进程之间的通信是必不可少的,所以有以下方式完成进程之间的通信:
操作。
线程在切换过程中,需要保存当前线程
、线程状态、堆栈、寄存器状态等信息。寄存器状态主要包括:
:堆栈指针,指向当前栈的栈顶指针。
:程序计数器,存储下一跳将要执行的指令。
:累加寄存器,用于加法乘法的缺省寄存器。
进程。因为同一进程内的线程会相互影响,所以如果一个用户的线程死掉了,其他用户的游戏也会崩溃。所以应该为每个用户开辟一个进程,使用户之间不会相互影响。
多线程模型适用于
密集型场合。因为经常会因为
阻塞来切换线程,而线程切换的系统开销比进程切换小。
多进程模型适用于需要频繁计算的场合。
并不是
并不是同时运行多个线程,而是轮流执行了,如果线程过多,
就要在不同的线程之间快速切换,那么
的利用率就会降低。
++ 线程中锁机制
仍然需要线程锁。
线程锁通常用来实现线程的同步和通信,在单核机器上仍然存在线程同步的问题。在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽以后,操作系统会把它挂起,然后运行另一个线程。如果两个线程共享某些数据,但没有线程锁,可能会导致同享数据修改引起冲突。
死锁是指两个或者两个以上进程在执行过程中,因为争夺资源而造成相互等待的现象。
四个发生条件分别为:
解决死锁的方法:
传统存储器存在问题:当有的作业很大或同一时间有大量作业要求运行时,其需要的内存空间超过了内存总容量,作业无法全部装入,导致作业无法运行。这都是由于传统存储器要求一次性装入作业导致的,所以采用了虚拟内存。
虚拟内存技术使进程在运行过程中,内存中只装入了当前要运行的少数页面,其余部分暂存在外存上。如果程序访问的页尚未调入内存中,便发出缺页中断,
将需要的页调入内存。如果内存满了,无法装入新的页时,便会使用页面置换方式将暂时不用的页调至外存,再将要访问的页调入内存。
虚拟内存的优点:
虚拟内存的缺点:
,需要耗费很大的时间。
请求分页存储管理中一般使用二级页表。
中取出页表地址。
位地址相加,获得想要的物理地址。
通过
分配内存时,只是分配了虚拟内存而不是实际的物理地址,进程访问时也是访问的虚拟地址而不是物理地址。
在请求分页系统中,可以查询页表的状态来确定要访问的页表是否在内存中。每当要访问的页面不存在内存中时,就会发生一个缺页中断,然后操作系统会将缺失的页调入到内存中。
缺页中断的处理一般分为
个步骤:
现场
现场并继续处理
当访问一个不存在内存中的页时,需要从外存调入。如果此时内存已满,就需要调出一个页到外存,在将需要的页调入。这个过程叫做缺页置换。
页面置换算法:置换掉最早调入内存的页面,也就是说在内存中按队列的形式管理页,从队尾插入,从队首删除。
置换算法:置换掉最近一段时间内最久未访问的页面。根据局部性原理,刚刚被访问过的页面可能马上又要被访问,而较长时间未访问的页面可能最近不会访问。
置换算法。置换掉最近一段时间访问次数最少的页面。
置换算法。为每一页设置一个访问位,再将页面设置成循环队列。在选择一个页面时,如果访问位是
,就把它置换掉,如果是
,就把访问位置为
并开始检查下一个页面。
是
的高速缓存,可以加快读取数据的速度。
在页式存储结构中,需要先访问内存获得数据物理地址,然后再去物理地址中读出数据。但访问内存的速度比较慢,所有引入了
,访问一个地址时,先去
中查找,如果命中了就可以直接获得其物理地址,然后访问数据,这样可以大大增加访问速度。
种
模型
。一直检查
事件是否就绪,没有就继续等待,期间什么事也不做。
。每隔一段时间检查一下
事件是否就绪,没有就绪就做其他事。
。安装一个信号处理函数,进程继续运行。当
事件就绪时,进程会收到
信号,然后处理
事件。
多路复用。
多路复用在阻塞
上多了个
函数,
函数可以看后面。
。应用程序把
请求给内核后,由内核去完成相关操作。当内核完成相关操作后,会发信号告诉应用进程本次
已经完成。
和
是水平触发的。
支持水平触发也支持边缘触发,但默认是水平触发的。
消息机制
当用户使用键盘或者鼠标时,系统会把这些操作转化成消息。系统会将这些消息放入消息队列中,然后对应的进程会循环从消息队列中取出消息,完成对应的操作。
函数来收集子进程的终止状态,并把他彻底销毁后返回,如果没有等到这样的一个子进程,就会阻塞在这里等待。
进程收养。
函数,那么这些进程的进程描述符仍然保存在系统中,这些进程被称为僵尸进程。
函数,此时就是僵尸进程,等到父进程处理以后才消失。如果父进程在子进程结束之前退出,子进程就会由
进程接管,
进程会以父进程的身份处理僵尸进程。
僵尸进程的危害:
如何避免僵尸进程
消灭产生僵尸进程的进程,那么僵尸进程就变成了孤儿进程,由
进程处理。
处理僵尸进程
:父进程
后马上
,子进程在
一次后马上
,孙进程完成父进程中本来要完成的事情,由于是孙进程的父进程已经退出了,它变成了孤儿进程,由
进程处理。
线程池就是首先创建一些线程,它们的集合称为线程池。线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。线程池可以很好的提高性能。
如果线程的空闲时间过长,就可以考虑缩小线程池。
可用通过
来判断系统是大端还是小端,因为
总是从低地址开始存放
int main() {
// freopen("in", "r", stdin);
union T {
int x;
char y;
} t;
t.x = 1;
if(t.y == 1) printf("xiao duan\n");
else printf("da duan\n");
return 0;
}
函数
函数通过系统调用创建一个和原来进程几乎一模一样的进程,两个进程可以做同一件事,如果传的参数不同也可以做不同的事。在子进程中,成功的
会返回
,在父进程中
会返回子进程的
,失败会返回负数。
的调用和作用和
是一致的。但存在一些区别:
的子进程拷贝父进程的地址空间,
的子进程和父进程共享地址空间。
的子进程和父进程执行顺序不定,
保证子进程先执行,父进程在执行。
函数是实现
多路复用的一种方式。
函数监听程序的文件描述符集,由数组来描述哪个文件描述符被置位了。当某个文件描述符就绪时,就会返回所有的描述符集,然后应用程序去检查哪个文件描述符上有事件发生。
函数还存在一些缺点:
函数通过一个可变长度的数组解决了
函数中文件描述符受限的问题。
函数把要监听的描述符添加进去,这些描述符会组成一颗红黑树。当某个描述符上有事件发生时,会把对应的文件描述符添加到链表中,然后返回链表。
相较与
的优点在于:
最大为
,
可以远远大于这个值。
返回时不可以把有事件的描述符筛选出来,需要在遍历一遍,而
返回时会加到一个链表中,然后直接对链表操作。
后父子进程的内存关系
进程内存空间
从高地址的到低地址分别为:
进程调度算法
核心空闲时,操作系统就从全局任务等待队列中选取就绪任务开始在此核心上执行。
核心利用率较高。
内核维护一个局部的任务等待队列。
内核空闲时,便从该核心的任务等待队列中选取恰当的任务执行。
核心间切换,有利于提高
核心局部
命中率。
操作系统采用的是基于全局队列的任务调度算法。
通过 (柱面号、盘面号、扇区号) 的三元组来定位到要读数据的位置。
一次读取数据需要的时间: