端口扫描nmap_mmap端口扫描状态

hacker|
106

同步与异步,阻塞与非阻塞的区别,以及select,poll和epoll

异步的概念和同步相对。

(1)当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后,才能进行后续的执行;

(2)当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。实际处理这个调用的部件在完成后,通过 状态、通知和回调 来通知调用者。

这里提到执行部件和调用者通过三种途径返回结果:状态、通知和回调。使用哪一种通知机制,依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制。

(A)阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务

(B)非阻塞调用是指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回

场景比喻:

举个例子,比如我去银行办理业务,可能会有两种方式:

在上面的场景中,如果:

a)如果选择排队(同步),且排队的时候什么都不干(线程被挂起,什么都干不了),是同步阻塞模型;

b)如果选择排队(同步),但是排队的同时做与办银行业务无关的事情,比如抽烟,(线程没有被挂起,还可以干一些其他的事),是同步非阻塞模型;

c)如果选择拿个小票,做在位置上等着叫号(通知),但是坐在位置上什么都不干(线程被挂起,什么都干不了),这是异步阻塞模型;

d)如果选择那个小票,坐在位置上等着叫号(通知),但是坐着的同时还打 *** 谈生意(线程没有被挂起,还可以干其他事情),这是异步非阻塞模型。

对这四种模型做一个总结:

1:同步阻塞模型,效率更低,即你专心排队,什么都不干。

2:异步阻塞,效率也非常低,即你拿着号等着被叫(通知),但是坐那什么都不干

3:同步非阻塞,效率其实也不高,因为涉及到线程的来回切换。即你在排队的同时打 *** 或者抽烟,但是你必须时不时得在队伍中挪动。程序需要在排队和打 *** 这两种动作之间来回切换,系统开销可想而知。

4:异步非阻塞,效率很高,你拿着小票在那坐着等叫号(通知)的同时,打 *** 谈你的生意。

linux下几个基本概念

1:用户控件和内核空间。 现代操作系统都是采用虚拟存储器,在32位操作系统下,它的寻址空间(虚拟存储空间)为4G(2的32次方)。为了保证用户进程补鞥呢直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。对linux操作系统而言,将更高的1G字节空间分给了内核使用,称为内核空间,将较低的3G字节的空间划分为用户空间。

2:进程切换很耗资源 ,为了控制进程的执行,内核必须有能力挂起正在cpu上运行的进程,并恢复以前挂起的某个进程的执行,这种行为叫进程的切换。每次切换,要保存上一个的上下文环境等等,总之记住进程切换很耗资源。

3:文件描述符 :文件描述符在形式上是一个非负整数。实际上,他是一个索引,指向内核为每个进程所维护的该进程打开文件的记录表。当程序打开一个文件时,内核就会向进程返回一个非负整数的文件描述符。但是文件描述符一般在unix,linux系统中才讲。

缓存IO ,大多数系统的默认IO操作都是缓存IO,在linux的缓存IO机制中,操作系统会将IO的数据缓存在系统的页缓存(page cache)中,也就是说,数据会先被拷贝到操作系统内核的缓冲区,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。 缓存IO的缺点: 数据在传输过程中需要在应用程序和地址空间和内核进行多次数据拷贝操作,这种数据拷贝操作锁带来的cpu以及内存消耗是很大的。

LINUX的IO模型

*** IO的本质是socket的读取。socket在linux系统被抽象为流,故对 *** IO的操作可以理解为对流的操作。

对于一次IO访问,比如以read操作为例, 数据会先被拷贝到操作系统内核的缓冲区,然后才会从内核缓冲区拷贝到进程的用户层,即应用程序的地址空间 。故当一个read操作发生时,其实是经历了两个阶段:

1:内核缓冲区的数据就位

2:数据从内核缓冲区拷贝到用户程序地址空间

那么具体到socket io的一次read操来说,这两步分别是:

1:等待 *** 上的数据分组到达,然后复制到内核缓冲区中

2:数据从内核缓冲区拷贝到用户程序的地址空间(缓冲区)

所以说 *** 应用要处理的无非就两个问题: *** IO和数据计算 ,一般来说 *** io带来的延迟影响比较大。

*** IO的模型大致有如下几种:

熟悉不? 我们常说的select,poll和epoll就是属于同步模型中多路复用IO的不同实现 *** 罢了。 下面分别对同步阻塞,同步不阻塞,同步io复用进行说明。

一:同步阻塞

它是最简单也最常用的 *** IO模型。linux下默认的socket都是blocking的。

从图中可以看到,用户进程调用recvfrom这个系统调用后,就处于阻塞状态。然后kernel就开始了IO的之一个阶段:数据准备。等之一个阶段准备完成之后,kernel开始第二阶段,将数据从内核缓冲区拷贝到用户程序缓冲区(需要花费一定时间)。然后kernel返回结果(确切的说是recvfrom这个系统调用函数返回结果),用户进程才结束blocking,重新运行起来。

总结 : 同步阻塞模型下,用户程序在kernel执行io的两个阶段都被blocking住了 。但是优点也是因为这个,无延迟能及时返回数据,且程序模型简单。

二:同步非阻塞

同步非阻塞就是隔一会瞄一下的轮询方式。同步非阻塞模式其实是可以看做一小段一小段的同步阻塞模式。

三:IO多路复用

由于同步非阻塞方式需要不断的轮询,光轮询就占据了很大一部分过程,且消耗cpu资源。而这个用户进程可能不止对这个socket的read,可能还有对其他socket的read或者write操作,那人们就想到了一次轮询的时候,不光只查询询一个socket fd,而是在一次轮询下,查询多个任务的socket fd的完成状态,只要有任何一个任务完成,就去处理它。而且,轮询人不是进程的用户态,而是有人帮忙就好了。那么这就是所谓的 IO多路复用 。总所周知的linux下的select,poll和epoll就是这么干的。。。

selelct调用是内核级别的,selelct轮询相比较同步非阻塞模式下的轮询的区别为: 前者可以等待多个socket,能实现同时对多个IO端口的监听 ,当其中任何一个socket数据准备好了,就返回可读。 select或poll调用之后,会阻塞进程 ,与blocking IO 阻塞不用在于,此时的select不是等到所有socket数据达到再处理,而是某个socket数据就会返回给用户进程来处理。

其实select这种相比较同步non-blocking的效果在单个任务的情况下可能还更差一些 ,因为这里调用了select和recvfrom两个system call,而non-blocking只调用了一个recvfrom,但是 用select的优势在于它可以同时处理多个socket fd 。

在io复用模型下,对于每一个socket,一般都设置成non-blocking,但是其实 整个用户进程是一直被block的 ,只不过用户process不是被socket IO给block住,而是被select这个函数block住的。

与多进程多线程技术相比,IO多路复用的更大优势是系统开销小。

一:select

select函数监视多个socket fs,直到有描述符就绪或者超时,函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。select的基本流程为:

二:poll

poll本质上跟select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd的状态,如果某个fd的状态为就绪,则将此fd加入到等待队列中并继续遍历。如果遍历完所有的fd后发现没有就绪的,则挂起当前进程,直到设备就绪或者主动超时。被唤醒后它又要再次遍历fd。

特点:

1:poll没有更大连接数限制,因为它是用基于链表来存储的,跟selelct直接监听fd不一样。

2:同样的大量的fd的数组被整体复制与用户态和内核地址空间之间。

3:poll还有一个特点是水平触发:如果报告了fd后没有被处理,则下次poll时还会再次报告该fd。

4:跟select一样,在poll返回后,还是需要通过遍历fdset来获取已经就绪的socket。当fd很多时,效率会线性下降。

三:epoll

epoll支持水平触发和边缘触发,更大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

没有更大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。

效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即Epoll更大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的 *** 环境中,Epoll的效率就会远远高于select和poll。

内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

聊聊同步、异步、阻塞与非阻塞

聊聊Linux 五种IO模型

聊聊IO多路复用之select、poll、epoll详解

IO模型及select,poll,epoll和kqueue的区别

(一)首先,介绍几种常见的I/O模型及其区别,如下:

blocking I/O

nonblocking I/O

I/O multiplexing (select and poll)

signal driven I/O (SIGIO)

asynchronous I/O (the POSIX aio_functions)—————异步IO模型更大的特点是 完成后发回通知。

阻塞与否,取决于实现IO交换的方式。

异步阻塞是基于select,select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄.

异步非阻塞直接在完成后通知,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。

1 blocking I/O

这个不用多解释吧,阻塞套接字。下图是它调用过程的图示:

重点解释下上图,下面例子都会讲到。首先application调用 recvfrom()转入kernel,注意kernel有2个过程,wait for data和copy data from kernel to user。直到最后copy complete后,recvfrom()才返回。此过程一直是阻塞的。

2 nonblocking I/O:

与blocking I/O对立的,非阻塞套接字,调用过程图如下:

可以看见,如果直接操作它,那就是个轮询。。直到内核缓冲区有数据。

3 I/O multiplexing (select and poll)

最常见的I/O复用模型,select。

select先阻塞,有活动套接字才返回。与blocking I/O相比,select会有两次系统调用,但是select能处理多个套接字。

4 signal driven I/O (SIGIO)

只有UNIX系统支持,感兴趣的课查阅相关资料

与I/O multiplexing (select and poll)相比,它的优势是,免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理。

5 asynchronous I/O (the POSIX aio_functions)

很少有*nix系统支持,windows的IOCP则是此模型

完全异步的I/O复用机制,因为纵观上面其它四种模型,至少都会在由kernel copy data to appliction时阻塞。而该模型是当copy完成后才通知application,可见是纯异步的。好像只有windows的完成端口是这个模型,效率也很出色。

6 下面是以上五种模型的比较

可以看出,越往后,阻塞越少,理论上效率也是更优。

=====================分割线==================================

5种模型的比较比较清晰了,剩下的就是把select,epoll,iocp,kqueue按号入座那就OK了。

select和iocp分别对应第3种与第5种模型,那么epoll与kqueue呢?其实也于select属于同一种模型,只是更高级一些,可以看作有了第4种模型的某些特性,如callback机制。

为什么epoll,kqueue比select高级?

答案是,他们无轮询。因为他们用callback取代了。想想看,当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

windows or *nix (IOCP or kqueue/epoll)?

诚然,Windows的IOCP非常出色,目前很少有支持asynchronous I/O的系统,但是由于其系统本身的局限性,大型服务器还是在UNIX下。而且正如上面所述,kqueue/epoll 与 IOCP相比,就是多了一层从内核copy数据到应用层的阻塞,从而不能算作asynchronous I/O类。但是,这层小小的阻塞无足轻重,kqueue与epoll已经做得很优秀了。

提供一致的接口,IO Design Patterns

实际上,不管是哪种模型,都可以抽象一层出来,提供一致的接口,广为人知的有ACE,Libevent(基于reactor模式)这些,他们都是跨平台的,而且他们自动选择更优的I/O复用机制,用户只需调用接口即可。说到这里又得说说2个设计模式,Reactor and Proactor。见:Reactor模式--VS--Proactor模式。Libevent是Reactor模型,ACE提供Proactor模型。实际都是对各种I/O复用机制的封装。

Java nio包是什么I/O机制?

现在可以确定,目前的java本质是select()模型,可以检查/jre/bin/nio.dll得知。至于java服务器为什么效率还不错。。我也不得而知,可能是设计得比较好吧。。-_-。

=====================分割线==================================

总结一些重点:

只有IOCP是asynchronous I/O,其他机制或多或少都会有一点阻塞。

select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

epoll, kqueue、select是Reacor模式,IOCP是Proactor模式。

java nio包是select模型。。

(二)epoll 与select的区别

1. 使用多进程或者多线程,但是这种 *** 会造成程序的复杂,而且对与进程与线程的创建维护也需要很多的开销。(Apache服务器是用的子进程的方式,优点可以隔离用户) (同步阻塞IO)

2.一种较好的方式为I/O多路转接(I/O multiplexing)(貌似也翻译多路复用),先构造一张有关描述符的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的。

区别(epoll相对select优点)主要有三:

1.select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024 表示select最多同时监听1024个fd。而epoll没有,它的限制是更大的打开文件句柄数目。

2.epoll的更大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个"伪"AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。

3.使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

关于epoll工作模式ET,LT

epoll有两种工作方式

ET:Edge Triggered,边缘触发。仅当状态发生变化时才会通知,epoll_wait返回。换句话,就是对于一个事件,只通知一次。且只支持非阻塞的socket。

LT:Level Triggered,电平触发(默认工作方式)。类似select/poll,只要还有没有处理的事件就会一直通知,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll.支持阻塞和不阻塞的socket。

三 Linux并发 *** 编程模型

1 Apache 模型,简称 PPC ( Process Per Connection ,):为每个连接分配一个进程。主机分配给每个连接的时间和空间上代价较大,并且随着连接的增多,大量进程间切换开销也增长了。很难应对大量的客户并发连接。

2 TPC 模型( Thread Per Connection ):每个连接一个线程。和PCC类似。

3 select 模型:I/O多路复用技术。

.1 每个连接对应一个描述。select模型受限于 FD_SETSIZE即进程更大打开的描述符数linux2.6.35为1024,实际上linux每个进程所能打开描数字的个数仅受限于内存大小,然而在设计select的系统调用时,却是参考FD_SETSIZE的值。可通过重新编译内核更改此值,但不能根治此问题,对于百万级的用户连接请求 即便增加相应 进程数, 仍显得杯水车薪呀。

.2select每次都会扫描一个文件描述符的 *** ,这个 *** 的大小是作为select之一个参数传入的值。但是每个进程所能打开文件描述符若是增加了 ,扫描的效率也将减小。

.3内核到用户空间,采用内存复制传递文件描述上发生的信息。

4 poll 模型:I/O多路复用技术。poll模型将不会受限于FD_SETSIZE,因为内核所扫描的文件 描述符 *** 的大小是由用户指定的,即poll的第二个参数。但仍有扫描效率和内存拷贝问题。

5 pselect模型:I/O多路复用技术。同select。

6 epoll模型:

.1)无文件描述字大小限制仅与内存大小相关

.2)epoll返回时已经明确的知道哪个socket fd发生了什么事件,不用像select那样再一个个比对。

.3)内核到用户空间采用共享内存方式,传递消息。

四 :FAQ

1、单个epoll并不能解决所有问题,特别是你的每个操作都比较费时的时候,因为epoll是串行处理的。 所以你有还是必要建立线程池来发挥更大的效能。

2、如果fd被注册到两个epoll中时,如果有时间发生则两个epoll都会触发事件。

3、如果注册到epoll中的fd被关闭,则其会自动被清除出epoll监听列表。

4、如果多个事件同时触发epoll,则多个事件会被联合在一起返回。

5、epoll_wait会一直监听epollhup事件发生,所以其不需要添加到events中。

6、为了避免大数据量io时,et模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在下边轮询ready fd列表。

如何写linux pci设备驱动程序

Linux下PCI设备驱动开发

1. 关键数据结构

PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。

Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单地使用数组下标来表示次设备号。

在PCI驱动程序中,下面几个关键数据结构起着非常核心的作用:

pci_driver

这个数据结构在文件include/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以及用于检测设备的函数probe( )和卸载设备的函数remove( ):

struct pci_driver {

struct list_head node;

char *name;

const struct pci_device_id *id_table;

int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);

void (*remove) (struct pci_dev *dev);

int (*save_state) (struct pci_dev *dev, u32 state);

int (*suspend)(struct pci_dev *dev, u32 state);

int (*resume) (struct pci_dev *dev);

int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);

};

pci_dev

这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的

硬件信息,包括厂商ID、设备ID、各种资源等:

struct pci_dev {

struct list_head global_list;

struct list_head bus_list;

struct pci_bus *bus;

struct pci_bus *subordinate;

void *sysdata;

struct proc_dir_entry *procent;

unsigned int devfn;

unsigned short vendor;

unsigned short device;

unsigned short subsystem_vendor;

unsigned short subsystem_device;

unsigned int class;

u8 hdr_type;

u8 rom_base_reg;

struct pci_driver *driver;

void *driver_data;

u64 dma_mask;

u32 current_state;

unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];

unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];

unsigned int irq;

struct resource resource[DEVICE_COUNT_RESOURCE];

struct resource dma_resource[DEVICE_COUNT_DMA];

struct resource irq_resource[DEVICE_COUNT_IRQ];

char name[80];

char slot_name[8];

int active;

int ro;

unsigned short regs;

int (*prepare)(struct pci_dev *dev);

int (*activate)(struct pci_dev *dev);

int (*deactivate)(struct pci_dev *dev);

};

2. 基本框架

在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关键模块是如何组织起来的。

/* 指明该驱动程序适用于哪一些PCI设备 */

static struct pci_device_id demo_pci_tbl [] __initdata = {

{PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,

PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},

{0,}

};

/* 对特定PCI设备进行描述的数据结构 */

struct demo_card {

unsigned int magic;

/* 使用链表保存所有同类的PCI设备 */

struct demo_card *next;

/* ... */

}

/* 中断处理模块 */

static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

/* ... */

}

/* 设备文件操作接口 */

static struct file_operations demo_fops = {

owner: THIS_MODULE, /* demo_fops所属的设备模块 */

read: demo_read, /* 读设备操作*/

write: demo_write, /* 写设备操作*/

ioctl: demo_ioctl, /* 控制设备操作*/

mmap: demo_mmap, /* 内存重映射操作*/

open: demo_open, /* 打开设备操作*/

release: demo_release /* 释放设备操作*/

/* ... */

};

/* 设备模块信息 */

static struct pci_driver demo_pci_driver = {

name: demo_MODULE_NAME, /* 设备模块名称 */

id_table: demo_pci_tbl, /* 能够驱动的设备列表 */

probe: demo_probe, /* 查找并初始化设备 */

remove: demo_remove /* 卸载设备模块 */

/* ... */

};

static int __init demo_init_module (void)

{

/* ... */

}

static void __exit demo_cleanup_module (void)

{

pci_unregister_driver(demo_pci_driver);

}

/* 加载驱动程序模块入口 */

module_init(demo_init_module);

/* 卸载驱动程序模块入口 */

module_exit(demo_cleanup_module);

上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。需要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。

3. 初始化设备模块

在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:

检查PCI总线是否被Linux内核支持;

检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。

读出配置头中的信息提供给驱动程序使用。

当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进行初始化时,一般都会调用如下的代码:

static int __init demo_init_module (void)

{

/* 检查系统是否支持PCI总线 */

if (!pci_present())

return -ENODEV;

/* 注册硬件驱动程序 */

if (!pci_register_driver(demo_pci_driver)) {

pci_unregister_driver(demo_pci_driver);

return -ENODEV;

}

/* ... */

return 0;

}

驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到了一个非0的返回值,那么驱动程序就必须得中止自己的任务了。在2.4以前的内核中,需要手工调用pci_find_device( )函数来查找PCI设备,但在2.4以后更好的办法是调用pci_register_driver( )函数来注册PCI设备的驱动程序,此时需要提供一个pci_driver结构,在该结构中给出的probe探测例程将负责完成对硬件的检测工作。

static int __init demo_probe(struct pci_dev *pci_dev, const struct

pci_device_id *pci_id)

{

struct demo_card *card;

/* 启动PCI设备 */

if (pci_enable_device(pci_dev))

return -EIO;

/* 设备DMA标识 */

if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {

return -ENODEV;

}

/* 在内核空间中动态申请内存 */

if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {

printk(KERN_ERR "pci_demo: out of memory\n");

return -ENOMEM;

}

memset(card, 0, sizeof(*card));

/* 读取PCI配置信息 */

card-iobase = pci_resource_start (pci_dev, 1);

card-pci_dev = pci_dev;

card-pci_id = pci_id-device;

card-irq = pci_dev-irq;

card-next = devs;

card-magic = DEMO_CARD_MAGIC;

/* 设置成总线主DMA模式 */

pci_set_master(pci_dev);

/* 申请I/O资源 */

request_region(card-iobase, 64, card_names[pci_id-driver_data]);

return 0;

}

4. 打开设备模块

在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。

static int demo_open(struct inode *inode, struct file *file)

{

/* 申请中断,注册中断处理程序 */

request_irq(card-irq, demo_interrupt, SA_SHIRQ,

card_names[pci_id-driver_data], card)) {

/* 检查读写模式 */

if(file-f_mode FMODE_READ) {

/* ... */

}

if(file-f_mode FMODE_WRITE) {

/* ... */

}

/* 申请对设备的控制权 */

down(card-open_sem);

while(card-open_mode file-f_mode) {

if (file-f_flags O_NONBLOCK) {

/* NONBLOCK模式,返回-EBUSY */

up(card-open_sem);

return -EBUSY;

} else {

/* 等待调度,获得控制权 */

card-open_mode |= f_mode (FMODE_READ | FMODE_WRITE);

up(card-open_sem);

/* 设备打开计数增1 */

MOD_INC_USE_COUNT;

/* ... */

}

}

}

5. 数据读写和控制信息模块

PCI设备驱动程序可以通过demo_fops 结构中的函数demo_ioctl( ),向应用程序提供对硬件进行控制的接口。例如,通过它可以从I/O寄存器里读取一个数据,并传送到用户空间里:

static int demo_ioctl(struct inode *inode, struct file *file, unsigned int

cmd, unsigned long arg)

{

/* ... */

switch(cmd) {

case DEMO_RDATA:

/* 从I/O端口读取4字节的数据 */

val = inl(card-iobae + 0x10);

/* 将读取的数据传输到用户空间 */

return 0;

}

/* ... */

}

事实上,在demo_fops里还可以实现诸如demo_read( )、demo_mmap( )等操作,Linux内核源码中的driver目录里提供了许多设备驱动程序的源代码,找那里可以找到类似的例子。在对资源的访问方式上,除了有I/O指令以外,还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作,另一方面也可以通过总线主DMA(Bus Master DMA)的方式让设备把数据通过DMA传送到系统内存中。

6. 中断处理模块

PC的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。

static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

struct demo_card *card = (struct demo_card *)dev_id;

u32 status;

spin_lock(card-lock);

/* 识别中断 */

status = inl(card-iobase + GLOB_STA);

if(!(status INT_MASK))

{

spin_unlock(card-lock);

return; /* not for us */

}

/* 告诉设备已经收到中断 */

outl(status INT_MASK, card-iobase + GLOB_STA);

spin_unlock(card-lock);

/* 其它进一步的处理,如更新DMA缓冲区指针等 */

}

7. 释放设备模块

释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反:

static int demo_release(struct inode *inode, struct file *file)

{

/* ... */

/* 释放对设备的控制权 */

card-open_mode = (FMODE_READ | FMODE_WRITE);

/* 唤醒其它等待获取控制权的进程 */

wake_up(card-open_wait);

up(card-open_sem);

/* 释放中断 */

free_irq(card-irq, card);

/* 设备打开计数增1 */

MOD_DEC_USE_COUNT;

/* ... */

}

8. 卸载设备模块

卸载设备模块与初始化设备模块是相对应的,实现起来相对比较简单,主要是调用函数pci_unregister_driver( )从Linux内核中注销设备驱动程序:

static void __exit demo_cleanup_module (void)

{

pci_unregister_driver(demo_pci_driver);

}

小结

PCI总线不仅是目前应用广泛的计算机总线标准,而且是一种兼容性最强、功能最全的计算机总线。而Linux作为一种新的操作系统,其发展前景是无法估量的,同时也为PCI总线与各种新型设备互连成为可能。由于Linux源码开放,因此给连接到PCI总线上的任何设备编写驱动程序变得相对容易。本文介绍如何编译Linux下的PCI驱动程序,针对的内核版本是2.4。

Kafka 安装教程 + nodejs 连接

1、kafka安装包

2、zookeeper安装包

1、先安装运行zookeeper

2、安装运行kafka

修改下图ip

advertised.listeners=PLAINTEXT://71.24.89.191:9092

这里主要是检测对应的端口是否是打开状态

分别是 zookeeper的默认端口 2181 和 kafka的 9092

检测网址

1、 添加

2、查看所有主题

3、查看主题下所有分区

4、动态 修改主题 分区为12

小伙伴们可以看我另一片文章

Nodejs kafka连接

1、内存不足

这里因为我的机器的内存比较小

OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000e0000000, 536870912, 0) failed; error='Cannot allocate memory' (errno=12)

There is insufficient memory for the Java Runtime Environment to continue.

Native memory allocation (mmap) failed to map 536870912 bytes for committing reserved memory.

我们只需要把kafka启动脚本修改一下就可以了

修改内存为256:

select和epoll的区别

先说下本文框架,先是问题引出,然后概括两个机制的区别和联系,最后介绍每个接口的用法

一、问题引出 联系区别

问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种:

1.使用多进程或者多线程,但是这种 *** 会造成程序的复杂,而且对与进程与线程的创建维护也需要很多的开销。(Apache服务器是用的子进程的方式,优点可以隔离用户)

2.用一个进程,但是使用非阻塞的I/O读取数据,当一个I/O不可读的时候立刻返回,检查下一个是否可读,这种形式的循环为轮询(polling),这种 *** 比较浪费CPU时间,因为大多数时间是不可读,但是仍花费时间不断反复执行read系统调用。

3.异步I/O(asynchronous I/O),当一个描述符准备好的时候用一个信号告诉进程,但是由于信号个数有限,多个描述符时不适用。

4.一种较好的方式为I/O多路转接(I/O multiplexing)(貌似也翻译多路复用),先构造一张有关描述符的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的。

区别(epoll相对select优点)主要有三:

1.select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024 表示select最多同时监听1024个fd。而epoll没有,它的限制是更大的打开文件句柄数目。

2.epoll的更大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个"伪"AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。

3.使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

二、接口

1)select

1. int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);

struct timeval{

long tv_sec;

long tv_usec;

}

有三种情况:tvptr == NULL 永远等待;tvptr-tv_sec == 0 tvptr-tv_usec == 0 完全不等待;不等于0的时候为等待的时间。select的三个指针都可以为空,这时候select提供了一种比sleep更精确的定时器。注意select的之一个参数maxfdp1并不是描述符的个数,而是更大的描述符加1,一是起限 *** 用,防止出错,二来可以给内核轮询的时候提供一个上届,提高效率。select返回-1表示出错,0表示超时,返回正值是所有的已经准备好的描述符个数(同一个描述符如果读和写都准备好,对结果影响是+2)。

2.int FD_ISSET(int fd, fd_set *fdset); fd在描述符 *** 中非0,否则返回0

3.int FD_CLR(int fd, fd_set *fd_set); int FD_SET(int fd, fd_set *fdset) ;int FD_ZERO(fd_set *fdset);

用一段linux 中man里的话“FD_ZERO() clears a set.FD_SET() and FD_CLR() respectively add and remove a given file descriptor from a set. FD_ISSET() tests to see if a file descriptor is part of the set; this is useful after select() returns.”这几个函数与描述符的0和1没关系,只是添加删除检测描述符是否在set中。

2)epoll

1.int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的之一个参数,给出更大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。之一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {

__uint32_t events; /* Epoll events */

epoll_data_t data; /* User data variable */

};

events可以是以下几个宏的 *** :

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

关于epoll工作模式ET,LT

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了,但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的 *** ,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

三、参考:

APUE(I/O多路转接)

linux man epoll select

我用PPLIVE看比赛,为什么诺顿总提示阻止了一次入侵企图?入侵:MMap Xmas Scan是什么意思?

不是的.开着卡巴斯基有一些在线电影网站也看不了的.那是杀毒软件和播放器的冲突遭成的.关了杀毒软件后,一切就好了.

MMap Xmas Scan是某个cn在扫描你的端口,准备进入你的电脑.

0条大神的评论

发表评论