目录
大端小端
https://www.cnblogs.com/luxiaoxun/archive/2012/09/05/2671697.html
https://blog.csdn.net/ce123_zhouwei/article/details/6971544 特别详细的介绍
以低位字节来记忆,它被放在低地址就是小端,被放在高地址就是大端
- 大端:低位字节排放在内存的高地址端,高位字节排放在内存的低地址端。
- 小端:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
程序验证:
- 1、int转char
int i = 1;
char *p = (char *)&i;
if(*p == 1)
printf("小端");
else
printf("大端");
大小端存储问题,如果小端方式中(i占至少两个字节的长度)则i所分配的内存最小地址那个字节中就存着1,其他字节是0.大端的话则1在i的最高地址字节处存放,char是一个字节,所以强制将char型量p指向i则p指向的一定是i的最低地址,那么就可以判断p中的值是不是1来确定是不是小端。
- 2、union
联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性就可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。
/*return 1: little-endian, return 0: big-endian*/
int checkCPUendian()
{
union
{
unsigned int a;
unsigned char b;
}c;
c.a = 1;
return (c.b == 1);
}
常见的字节序
一般操作系统都是小端,而通讯协议是大端的。
- 常见CPU的字节序
大端 : PowerPC、IBM、Sun
小端 : x86、DEC
ARM既可以工作在大端模式,也可以工作在小端模式。
- 常见文件的字节序
大端:Adobe PS、JPEG、MacPaint
小端:BMP、GIF、RTF
另外,Java和所有的网络通讯协议都是使用Big-Endian的编码。
原码、反码、补码
http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html讲的比较细,有讲采用补码的原理和证明
https://blog.csdn.net/vickyway/article/details/48788769(还没仔细看)
原码:符号位加上数值的绝对值的二进制数,正数的符号位为0,负数的符号位为1
反码:正数的反码是原码本身,负数的反码是符号位不变,其他位按位取反
补码:负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
共享内存如何实现?
https://chyyuu.gitbooks.io/simple_os_book/content/zh/chapter-3/implement_shared_mem.html实现共享内存
https://blog.csdn.net/killmice/article/details/41516533实现共享内存同步的四种方法
进程、线程
进程是一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
进程的特点
- 1、动态性
可动态的创建、结束进程
- 2、并发性
在一段时间内进程“看起来”是同时执行的。
注意区别与“并行”:并行是在同一时刻内多个进程一起执行
- 3、独立性
不同的进程之间正常运行互不影响,在操作系统中通过页表可以使不同的程序访问不同的地址空间,且可以保证其不能越过自己所访问的地址空间,一旦越过就会产生缺页异常或是页错误,
- 4、制约性
进程控制结构
进程的状态转换
https://blog.csdn.net/baidu_35534327/article/details/54317945
https://blog.csdn.net/u011012049/article/details/47624719
进程有三种状态:就绪、执行、阻塞
三种状态的转换如下:
- 就绪->执行(调度)
就绪状态是指进程获得了调度程序为之分配的除了CPU时间之外的其他必要资源,只要CPU时间一到位就可执行了。
处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转变成执行状态。
- 执行->就绪(时间片到了)
处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从执行状态转变成就绪状态。
- 执行->阻塞(等待某个事件发生)
正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成阻塞状态。
- 阻塞->就绪(因等待事件发生而唤醒)
处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态。
常用的进程调度算法
https://blog.csdn.net/fuzhongmin05/article/details/55802925讲的比较详细
https://blog.csdn.net/wanghao109/article/details/13004507图解
- 1、先来先服务(FCFS):也可以称为先进先出,进程一直到处理完才释放cpu
- 2、时间片轮转: 以一个周期性间隔产生时钟中断,此时当前正在运行的进程被置于就绪队列,基于FCFS选择下一个就绪进程 运行。
- 3、最短进程优先(SPN):下一次选择所需处理时间最短的进程
- 4、最短剩余时间优先(SRT):总是选择预期剩余时间最短的进程
- 5、最高响应比优先(HRRN):R=(w+s)/s,其中R表示响应比,w表示已经等待的时间,s表示期待服务的时间
- 6、优先权调度算法:先处理优先级最高的。
- 7、多级反馈队列调度算法:
设置多个就绪队列,并为各个队列赋予不同的优先级,优先级高的时间片短,初始进程放在最高优先级队列,它在一个时间片结束时尚未完成,则将其放入下一个优先级队列,直到处于最低队列(最低优先级队列的进程反复回到该队列)
进程第一次进入系统是放置于RQ0,第一次被强占并返回就绪态时,放入RQ1,以后每次被强占就下降一级。如果进程处于最低等级,则不再降级,反复返回到该队列,直到结束。
进程和线程的区别,对比
https://blog.csdn.net/mxsgoden/article/details/8821936
http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html特别详细,但是以C#.net讲解的
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
6)进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
线程是否有独立的空间
进程间通信
通信的目的
-
数据传输
一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 -
共享数据
多个进程想要操作共享数据,一个进程对共享数据 -
通知事
一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 -
资源共享
多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。 -
进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
通信方式
参考:https://blog.csdn.net/gatieme/article/details/50908749
https://www.jianshu.com/p/c1015f5ffa74写的比较详细
-
管道(pipe),流管道(s_pipe)和有名管道(FIFO)
-
信号(signal)
-
消息队列
-
共享内存
-
信号量
-
套接字(socket)
管道:
管道有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
流管道去除了第一种限制,可以双向传输.
有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;有名管道的名字存在于文件系统中,内容存放在内存中。
管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。该缓冲区可以看做是一个循环队列,读和写的位置都是自动增长的,不能随意改变,一个数据只能被读一次,读出来以后在缓冲区就不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或者写进程进入等待队列,当空的缓冲区有新数据写入或者满的缓冲区有数据读出来时,就唤醒等待队列中的进程继续读写。
信号:
信号是软件层次上对中断机制的一种模拟,是一种异步通信方式,,信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件主要有两个来源:
- 硬件来源:用户按键输入
Ctrl+C
退出、硬件异常如无效的存储访问等。 - 软件终止:终止进程信号、其他进程调用kill函数、软件异常产生信号。
总之:
- 信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
- 如果该进程当前并未处于执行状态,则该信号就有内核保存起来,知道该进程回复执行并传递给它为止。
- 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消是才被传递给进程。
Linux系统中常用信号:
(1)SIGHUP:用户从终端注销,所有已启动进程都将收到该进程。系统缺省状态下对该信号的处理是终止进程。
(2)SIGINT:程序终止信号。程序运行过程中,按Ctrl+C
键将产生该信号。
(3)SIGQUIT:程序退出信号。程序运行过程中,按Ctrl+\\
键将产生该信号。
(4)SIGBUS和SIGSEGV:进程访问非法地址。
(5)SIGFPE:运算中出现致命错误,如除零操作、数据溢出等。
(6)SIGKILL:用户终止进程执行信号。shell下执行kill -9
发送该信号。
(7)SIGTERM:结束进程信号。shell下执行kill 进程pid
发送该信号。
(8)SIGALRM:定时器信号。
(9)SIGCLD:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。
(信号量是变量,而信号是约定的固定的值)
消息队列:
- 消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。
- 与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
- 另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达
- 消息队列允许一个或多个进程向它写入与读取消息.
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势
- 消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺
- 目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
共享内存:(需要某种机制控制配合使用,如信号量)
- 使得多个进程可以可以直接读写同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
- 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
- 由于多个进程共享一段内存,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥。
信号量:
信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。
为了获得共享资源,进程需要执行下列操作:
- 创建一个信号量:这要求调用者指定初始值,对于二值信号量来说,它通常是1,也可是0。
- 等待一个信号量:该操作会测试这个信号量的值,如果小于0,就阻塞。也称为P操作。
- 挂出一个信号量:该操作将信号量的值加1,也称为V操作。
- 为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。
- Linux环境中,有三种类型:Posix(可移植性操作系统接口)有名信号量(使用Posix IPC名字标识)、Posix基于内存的信号量(存放在共享内存区中)、System V信号量(在内核中维护)
套接字:
套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
套接字是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
信号量与互斥量之间的区别
- 1、互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
- 2、互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
- 3、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
套接字特性
由3个属性确定,它们分别是:域、端口号、协议类型。
1)套接字的域
它指定套接字通信中使用的网络介质,最常见的套接字域有两种:
一是AF_INET,它指的是Internet网络。当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,所以在使用socket作为通信的终点,服务器应用程序必须在开始通信之前绑定一个端口,服务器在指定的端口等待客户的连接。
另一个域AF_UNIX,表示UNIX文件系统,它就是文件输入/输出,而它的地址就是文件名。
(2)套接字的端口号
每一个基于TCP/IP网络通讯的程序(进程)都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于256的端口号保留给标准应用程序,比如pop3的端口号就是110,每一个套接字都组合进了IP地址、端口,这样形成的整体就可以区别每一个套接字。
(3)套接字协议类型
因特网提供三种通信机制,
一是流套接字,流套接字在域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。流套接字提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。
二个是数据报套接字,它不需要建立连接和维持一个连接,它们在域中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
三是原始套接字,原始套接字允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。
线程间的通信
https://www.jianshu.com/p/9218692cb209
- 1、使用全局变量
主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile - 2、使用消息实现通信
在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。 - 3、使用事件CEvent类实现线程间通信
Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
线程间的同步方式
各个线程可以访问进程中的公共变量,资源,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。数据之间的相互制约包括
1、直接制约关系,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着,这种关系可以称之为同步关系
2、间接制约关系,即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互斥是一种制约关系更小的同步
线程间的同步方式有四种
- 临界区
临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用EnterCriticalSection函数;当对保护数据的操作完成之后,调用LeaveCriticalSection函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。
PS:关键段对象会记录拥有该对象的线程句柄即其具有“线程所有权”概念,即进入代码段的线程在leave之前,可以重复进入关键代码区域。所以关键段可以用于线程间的互斥,但不可以用于同步(同步需要在一个线程进入,在另一个线程leave) - 互斥量
互斥与临界区很相似,但是使用时相对复杂一些(互斥量为内核对象),不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。
PS:1、互斥量由于也有线程所有权的概念,故也只能进行线程间的资源互斥访问,不能由于线程同步;
2、由于互斥量是内核对象,因此其可以进行进程间通信,同时还具有一个很好的特性,就是在进程间通信时完美的解决了"遗弃"问题 - 信号量
信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,PV操作
PS:事件可以完美解决线程间的同步问题,同时信号量也属于内核对象,可用于进程间的通信 - 事件
事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。由SetEvent()来触发,由ResetEvent()来设成未触发。
PS:事件是内核对象,可以解决线程间同步问题,因此也能解决互斥问题
死锁
产生死锁的4个必要条件
- 互斥使用(资源独占):
进程对其申请的资源进行排他控制,其他申请资源的进程必须等待
- 非剥夺控制(不可抢占):
占用资源的进程只能自己释放资源,即使该进程处于阻塞状态,它所占有的资源也不能被其他进程使用
- 零散请求:
进程可以按需逐次申请资源。这样在进程已经找有资源的情况下,尤申青其他资源而得不到满足时,并不释放已占有资源
- 循环等待:
等待资源的进程形成了一个封闭的链,链上的进程都在等待其他进程占有的资源
死锁的预防
破坏互斥条件
破坏不可剥夺条件
破坏零散请求
采用静态分配策略,得到了所需的所有资源后才能执行
破坏循环等待
给资源编号、进程必须从小到大的顺序申请资源
Linux IO模型
http://blog.51cto.com/noican/1354950
https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral
https://blog.csdn.net/zero__007/article/details/77540343
https://segmentfault.com/a/1190000003063859
fd 是(file descriptor),这种一般是BSD Socket的用法,用在Unix/Linux系统上。 在Unix/Linux系统下,一个socket句柄,可以看做是一个文件,在socket上收发数据,相当于对一个文件进行读写,所以一个socket句柄,通常也用表示文件句柄的fd来表示。
同步与异步
同步和异步关注的是消息通信机制。
所谓同步,就是在发出一个“调用”时,在没有得到结果之前,该“调用”就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由“调用者”主动等待这个“调用”的结果。 而异步则是相反,“调用”在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在“调用”发出后,“被调用者”通过状态、通知来通知调用者,或通过回调函数处理这个调用。
同步:执行一个操作之后,进程触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看IO操作(也就是我们说的非阻塞)是否完成,等待结果,然后才继续执行后续的操作。
异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
举个通俗的例子:你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程,进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。。
还是上面的例子,你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
同步和异步针对应用程序来,关注的是程序中间的协作关系;阻塞与非阻塞更关注的是单个进程的执行状态。
同步有阻塞和非阻塞之分,异步没有,它一定是非阻塞的
五种IO模型
我们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO、管道、终端,对我们来说,一切都是文件,一切都是流。在信息 交换的过程中,我们都是对这些流进行数据的收发操作,简称为I/O操作(input and output),往流中读出数据,系统调用read,写入数据,系统调用write。不过话说回来了 ,计算机里有这么多的流,我怎么知道要操作哪个流呢?对,就是文件描述符,即通常所说的fd,一个fd就是一个整数,所以,对这个整数的操作,就是对这个文件(流)的操作。我们创建一个socket,通过系统调用会返回一个文件描述符,那么剩下对socket的操作就会转化为对这个描述符的操作。
对于网络数据的接收操作而言,五种I/O模型都是分为两个阶段:
- 等待数据准备好。
- 将准备好的数据,从内核空间考到进程空间。
对于第一步,就是等待数据到达,到达之后,数据就被复制到内核缓冲区;对于第二步,将数据从内核缓冲区复制到进程缓冲区中。
阻塞、非阻塞、IO多路复用及信号都是同步的,在同步中第二阶段的数据复制都是阻塞的。异步的两个阶段都是非阻塞的。
(IO多路复用是阻塞的,信号驱动是非阻塞的)
-
阻塞式IO模型
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block的状态,重新运行起来。
-
非阻塞式IO模型
从图中可以看出,非阻塞IO通过进程反复调用IO函数(多次系统调用,并马上返回);在数据拷贝的过程中,进程是阻塞的。
当把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
-
IO多路复用模型
为什么提出IO多路复用?由于同步非阻塞方式需要不断主动轮询,轮询占据了很大一部分过程,轮询会消耗大量的CPU时间,而 “后台” 可能有多个任务在同时进行,人们就想到了循环查询多个任务的完成状态,只要有任何一个任务完成,就去处理它。如果轮询不是进程的用户态,而是有人帮忙就好了。那么这就是所谓的 “IO 多路复用”
。
I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select、poll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。这两个函数可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。
多路复用模型的思想是,当用户进程发起IO请求的时候,将请求到的fd交给内核命令select、poll或epoll去监控,select、poll会采用遍历的方式不断扫描持有的fd,当发现有fd已经就绪的时候,回调函数rollback。由于操作系统限制,select、poll函数能管理的fd个数有限制,默认为1024个;而epoll,它采用一种事件驱动的方式来回调rollback函数,这种方式比顺序扫描fd的效率更高,另外,epoll可以管理的fd个数不受操作系统的内核限制,它的最大值只与内存大小有关
select、poll的时间复杂度为O(n),epoll的时间复杂度是O(1)
-
信号驱动式IO模型
允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
信号驱动的IO模型,最开始不会先调用recvfrom函数,而是去调用sigaction函数,这个函数会生成并执行一个信号处理函数,并且马上返回,此时,应用进程可以继续进行工作,在这个阶段是非阻塞的,当请求的数据报准备就绪时,会生成一个SIGIO的信号通知应用进程调用recvfrom函数完成IO操作,这种模型可以让应用进程在内核准备数据的这段时间内可以继续工作,但是在数据报从内核向用户空间复制的这段时间仍然是阻塞的。
-
异步IO
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都 完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
信号驱动与异步IO模型的区别
- 从工作流程上来看,信号驱动的IO模型和异步IO模型,差别在于通知应用进程的时机,信号驱动IO模型的时机是在IO的数据报准备就绪之后,将数据从内核复制到用户空间之前,而异步IO模型是在数据报复制到用户空间完成之后;这里的异步指的是内核进程和应用进程之间的异步;
- 从驱动方式上来看,信号驱动的IO模型的驱动载体是fd,即数据准备就绪之后触发,而异步IO模型的驱动载体是事件,即当整个操作完成之后触发。
select、poll及epoll之间的区别
https://blog.csdn.net/zero__007/article/details/50672664
select
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
- 1、单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
- 2、对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
- 3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。消息传递方式:内核需要将消息传递到用户空间,都需要内核拷贝动作。
poll
poll 本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:
- 1、大量的fd的数组被整体复制于用户态和内核地址空间之 间,而不管这样的复制是不是有意义。
- 2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
epoll
epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
优点:
- 1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。
- 2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数。即Epoll最大的优点就在于它只管你“活跃”的连接数,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
- 3、内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。消息传递方式:epoll通过内核和用户空间共享一块内存来实现的。
总结:
综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。
- 1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
- 2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善.
水平触发和边缘触发
https://blog.csdn.net/lihao21/article/details/67631516
https://blog.csdn.net/fengxinlinux/article/details/75331567
磁盘调度算法
https://blog.csdn.net/Jaster_wisdom/article/details/52345674
- 1.FIFO(先来先服务算法):假设当前磁道在某一位置,依次处理服务队列里的每一个磁道,这样做的优点是处理起来比较简单,但缺点是磁头移动的距离和平均移动距离会很大
- 2.SSTF(最短寻道时间算法):这种算法的本质是利用贪心算法来实现,假设当前磁道在某一位置,接下来处理的是距离当前磁道最近的磁道号,处理完成之后再处理离这个磁道号最近的磁道号,直到所有的磁道号都服务完了程序结束。这样做的优点是性能会优于FIFO算法,但是会产生距离当前磁道较远的磁道号长期得不到服务,也就是“饥饿”现象,因为要求访问的服务的序列号是动态产生的,即各个应用程序可能不断地提出访问不同的磁道号的请求。
- 3.SCAN(电梯调度算法):先按照一个方向(比如从外向内扫描),扫描的过程中依次访问要求服务的序列。当扫描到最里层的一个服务序列时反向扫描,这里要注意,假设最里层为0号磁道,最里面的一个要求服务的序列是5号,访问完5号之后,就反向了,不需要再往里扫。结合电梯过程更好理解,在电梯往下接人的时候,明知道最下面一层是没有人的,它是不会再往下走的。
- 4.CSCAN(循环扫描算法):来看一下上一种算法,有什么问题。仔细一看,我们会发现,在扫描到最里面的要求服务的序列时,接着会反向,在接下来的很大一部分时间里,应该是没有要求服务的磁道号的,因为之前已经访问过了。什么意思,就是说从初始磁道号到最里层的一个磁道号之间的所有序列都已经访问过了,所以SCAN会增加等待的时间。为了解决这样的情况,CSCAN算法的思想是,访问完最里面一个要求服务的序列之后,立即回到最外层欲访问磁道。也就是始终保持一个方向。故也称之为单向扫描调度算法。从最里面的一个磁道立即回到最外层欲访问的磁道,这步的距离是两者磁道号差的绝对值
- 5.FSCAN(分步电梯调度算法(分两个队列)):算法思想是,在扫描的过程中所有新产生的序列放在另外的一个队列中,当访问完当前队列之后,再访问新产生的一个队列。这种算法可以有效防止磁壁粘着现象