精华内容
下载资源
问答
  • 2011-02-27 13:44:00

    端口限速:一般ingress端口会提供端口限速的功能,超过限制速率的包会被丢弃。

    端口整形:一般egress端口会提供端口流量整形的功能,除了可以限制速率以外,还可以对突发的流量起到缓冲作用。所以流量整形是比限速更高级的功能,它有两个参数:burst sizerate


    令牌桶原理

    有两个重要参数:

    CIR(承诺访问速率):即向令牌桶中填充令牌的速率

    CBS(承诺突发尺寸):即令牌桶的容量

    其原理如下:

    1.令牌以一定的速率放入桶中

    2.每个令牌代表一定数量的比特

    3.发送一个包后,流量调节器就要从桶中删除与包大小相等的令牌数。

    4.如果没有足够的令牌发送包,这个包就会等待直到有足够的令牌或被丢弃。


    更多相关内容
  • IO 端口和IO 内存(原理篇)

    千次阅读 2021-03-09 20:49:50
    一般厂商按照IO空间性质将IO划分为IO 端口和IO内存。 IO 端口 VS IO内存 两者差别如下: 两者划分按照空间是否与CPU空间独立划分: IO内存:IO内存又称为Memory-Mapped I/O(MMIO),该IO空间处在CPU空间范围内...

    CPU要想控制所链接的设备,不可避免需要通过IO(input/output)与外设打交道,CPU通过IO操纵设备上的寄存器等来实现对 设备的控制。一般厂商按照IO空间性质将IO划分为IO 端口和IO内存。

    IO 端口 VS IO内存

    两者差别如下:

     两者划分按照空间是否与CPU空间独立划分:

    • IO内存:IO内存又称为Memory-Mapped I/O(MMIO),该IO空间处在CPU空间范围内,IO内存和普通的内存没什么区别,两者都是通过CPU的地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。要想操纵该IO就得首先将该IO映射到CPU的地址中,然后就可以访问该IO,如同访问内存。大多数嵌入式设备都属于此。
    • IO端口:又称为Port(PIO),该IO的空间与CPU空间相互独立,两者互相独立,相互不干扰,这种类型IO在X86中比较常见,该IO端口有独立的空间,所以CPU要想访问该端口就得通过一些专有函数或者指令。

    IO端口

    IO端口拥有独立空间,与CPU 处在不同得空间,CPU如果要想访问该端口就需要使用专有的指令集,比如X86提供IN 和OUT指令集。

    linux内核也为访问IO端口提供了一系列函数和方法:

    在内核中如果要对某些端口进行操作,就要首先获取到访问该IO权限,以防止其他程序同时操作该端口。要获取端口端口权限可以使用request_region函数,该函数定义include/linux/ioport.h文件中:

    struct resource *request_region(unsigned long first, unsigned long n, const  char *name)

    • first:要获取的起始端口。如果要同时获取多个连续端口,则该参数为起始端口
    • n: 要获取端口数量
    • name:设备名字

     当获取到端口之后,可以在/proc/ioports文件中查看当前系统所有已经被分配的端口。

    当端口使用完毕或者驱动模块卸载时,需要将占用的端口给释放掉,以供其他程序使用,释放端口函数为release_region()函数:

    void release_region(unsigned long start, unsigned long n)

     从内核源码ioport.h文件中可以看到,其实上述两个函数为宏定义:

    ...
    #define request_region(start,n,name)		__request_region(&ioport_resource, (start), (n), (name), 0)
    
    ...
    #define release_region(start,n)	__release_region(&ioport_resource, (start), (n))

    真正起作用的为__request_region和    __release_region函数,其实所有以及被分配的IO端口在ioport_resource中进行管理,查看__request_region代码:

    
    /**
     * __request_region - create a new busy resource region
     * @parent: parent resource descriptor
     * @start: resource start address
     * @n: resource region size
     * @name: reserving caller's ID string
     * @flags: IO resource flags
     */
    struct resource * __request_region(struct resource *parent,
    				   resource_size_t start, resource_size_t n,
    				   const char *name, int flags)
    {
    	DECLARE_WAITQUEUE(wait, current);
    	struct resource *res = alloc_resource(GFP_KERNEL);
    	struct resource *orig_parent = parent;
    
    	if (!res)
    		return NULL;
    
    	res->name = name;
    	res->start = start;
    	res->end = start + n - 1;
    
    	write_lock(&resource_lock);
    
    	for (;;) {
    		struct resource *conflict;
    
    		res->flags = resource_type(parent) | resource_ext_type(parent);
    		res->flags |= IORESOURCE_BUSY | flags;
    		res->desc = parent->desc;
    
    		conflict = __request_resource(parent, res);
    		if (!conflict)
    			break;
    		/*
    		 * mm/hmm.c reserves physical addresses which then
    		 * become unavailable to other users.  Conflicts are
    		 * not expected.  Warn to aid debugging if encountered.
    		 */
    		if (conflict->desc == IORES_DESC_DEVICE_PRIVATE_MEMORY) {
    			pr_warn("Unaddressable device %s %pR conflicts with %pR",
    				conflict->name, conflict, res);
    		}
    		if (conflict != parent) {
    			if (!(conflict->flags & IORESOURCE_BUSY)) {
    				parent = conflict;
    				continue;
    			}
    		}
    		if (conflict->flags & flags & IORESOURCE_MUXED) {
    			add_wait_queue(&muxed_resource_wait, &wait);
    			write_unlock(&resource_lock);
    			set_current_state(TASK_UNINTERRUPTIBLE);
    			schedule();
    			remove_wait_queue(&muxed_resource_wait, &wait);
    			write_lock(&resource_lock);
    			continue;
    		}
    		/* Uhhuh, that didn't work out.. */
    		free_resource(res);
    		res = NULL;
    		break;
    	}
    	write_unlock(&resource_lock);
    
    	if (res && orig_parent == &iomem_resource)
    		revoke_devmem(res);
    
    	return res;
    }
    

    从上述代码中可以看到,将IO端口抽象成resource结构资源,里面存放着每次申请的起始端口和端口数量,如果本次申请的IO资源以及在ioport_resource存在,则说明存在资源冲突,造成申请失败。如果不存在冲突 ,则申请IO端口资源成功并将其加入到ioport_resource中。

    当IO端口占用成功之后,就可以对IO端口进行读写操作,需要用到专有的读写操作函数in和out系列函数:

    unsigned in[bwl](unsigned long port)
    void out[bwl](value, unsigned long port)
    • b:bytes操作IO端口数据为一个字节
    • w:word 操作IO端口数据为两个字节
    • l:long操作IO端口数据为四个字节

    另外还提供了参数为字符变量接口,比在外面使用C循环更加高效:

    void ins[bwl](unsigned port, void *addr, unsigned long count)
    void outs[bwl](unsigned port, void *addr, unsigned long count)

    例如 :从端口中读取一个8 bits:

    oldlcr = inb(baseio + UART_LCR)

    往一个端口中写入一个8 bit数据:

    outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR)

    上述为内核对IO端口操作的一个基本流程。

    IO内存

    IO内存又称为MMIO,该IO空间就是处于CPU的空间,原因就是占用了CPU的总线地址空间,其性质和普通的内存一样,由于访问时该IO时和访问内存一样都是物理地址,而在linux中并不会直接对物理地址进行操作,需要将其映射到虚拟地址中。由于linux属于宏内核,驱动位于内核中,一个驱动程序要想访问IO内存就必须将其映射到内核的虚拟地址空间中(linux在空间划分时会专门留出一段空间预留给IO使用),然后才能对IO进行读写操作。

    早期对IO内存操作过程和IO端口类似,首先需要调用request_mem_region函数将该IO内存资源占用,防止其他程序占用该IO:

    struct resource *request_mem_region(unsigned long start, unsigned long len,char *name)

    • start: 该IO的地址相当与物理地址,一般该地址是由芯片分配好,可以从使用的CPU datasheet中查到。
    • len: 申请IO数量,必须是start起始地址连续 len个字节
    • nane: 设备名称或者IO端口名称,用以标记该端口被谁使用

    占用完毕之后,可以使用/proc/iomem文件中查看当前系统IO内存使用情况。

     当使用完毕之后,可以使用release_mem_region()函数,将占用的IO释放掉。

    上述函数仅仅只是完成了对IO占用,并没有将其映射到内核的空间中,内核提供了ioremap函数,将其IO内存映射到内核的虚拟空间中:

    void *ioremap(unsigned long phys_addr, unsigned long size);

    ioremap返回值为映射到内核的虚拟地址,之后对该IO操作就使用映射之后的该虚拟地址进行操作。

    当使用完毕之后,可以使用iounmap将映射释放掉:

    void iounmap(void * addr)

    为了操作一个IO内存,首先需要取得所有权然后进行映射到虚拟地址空,操作起来其实不是太方便,上述方法已经在内核中废弃掉了,只是由于历史原因还有些旧的驱动在使用,后面驱动开发人员不要再进行使用。

    新的IO内存操作接口为devm_ioremap():

    void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,    resource_size_t size)

    该接口集成了获取IO所有权和映射功能,所以使用IO内存时只需要使用这一个接口即可

    释放端口可以使用:

    void devm_iounmap(struct device *dev, void __iomem *addr)

     虽然IO内存和普通内存地址一样,但是不建议直接使用指针对IO内存地址进行访问,使用专有的函数进行访问。

    针对不相同架构上可能访问方法不一样,比如针对PCI中的端口,都为小字节序,所以可以使用下面函数进行操作:

    unsigned read[bwl](void *addr);
    void write[bwl](unsigned val, void *addr);

    如果时一个raw,并且不需要字节转换可以使用下面接口进行操作:

    unsigned __raw_read[bwl](void *addr);
    void __raw_write[bwl](unsigned val, void *addr);

    现今大部分的IO都为小字节序,因为这样可以避免再次的字节序转换,从而提高效率。

    比如在:drivers/tty/serial/uartlite.c文件中对一个IO内存写入32bit数据:

    writel(c & 0xff, port->membase + 4);

    read[bwl]和write[bwl]历史缺陷 

     read[bwl]和write[bwl]系列函数实现位于各个芯片架构io.h文件中,不同的架构实现稍有差别,主要差异点在与其指令集的实现,include/asm-generic/io.h文件中。

    以writeb为例子,其源代码为:

    static inline void writeb(u8 value, volatile void __iomem *addr)
    {
    	__io_bw();
    	__raw_writeb(value, addr);
    	__io_aw();
    }

    可以看到最后本质上还是调用的__raw_writeb()函数:

    static inline void __raw_writeb(u8 value, volatile void __iomem *addr)
    {
    	*(volatile u8 __force *)addr = value;
    }

    LDD3指出read[bwl]和write[bwl]系列函数存在一些列的不安全问题:

    Other drivers, knowing that I/O memory addresses are not real pointers, store them in integer variables; that works until they encounter a system with a physical address space which doesn't fit into 32 bits. And, in any case, readb() and friends perform no type checking, and thus fail to catch errors which could be found at compile time.

    linus在邮件列表中指出,由于历史原因,在调用read/write时 很多驱动开发人员传入不是一个地址而是使用一个integer整型,那么在64位芯片时由于地址时8个字节,而不是整型4个字节,此时会出现问题

    mostly just because of historical reasons, and as a result some drivers didn't use a pointer at all, but some kind of integer. 
    Sometimes even one that couldn't _fit_ a MMIO address in it on a 64-bit machine.

    在2.6.9 kernel版本中对IO 内存提供了一系列新的API对IO进行操作

    unsigned int ioread8(void *addr);
    unsigned int ioread16(void *addr);
    unsigned int ioread32(void *addr);
    void iowrite8(u8 value, void *addr);
    void iowrite16(u16 value, void *addr);
    void iowrite32(u32 value, void *addr);
    
    //“string”字符串版本
    void ioread8_rep(void __iomem *port, void *buf, 
                                unsigned long count)

    再次特别指出笔者在最新的5.11.5内核版本中发现read和write函数最后的调用实际上和ioread和iowrite一样,笔者猜测可能时后面防止出现问题,在后面的版本中也将read和write函数缺陷给修改掉了,但是并没有在邮件列表中找到。

    为了论证笔者的猜测,笔者翻到了当初2.6.9版本中的解决方案说明:

    The 2.6.9 kernel will contain a series of changes designed to improve how the kernel works with I/O memory. The first of these is a new __iomem annotation used to mark pointers to I/O memory. These annotations work much like the __user markers, except that they reference a different address space. As with __user, the __iomem marker serves a documentation role in the kernel code; it is ignored by the compiler. When checking the code with sparse, however, developers will see a whole new set of warnings caused by code which mixes normal pointers with __iomem pointers, or which dereferences those pointers.

    在2.6.9的解决方案中,通过增加对IO memory 指针增加了一个 __iomem修饰,在编译时对地址进行检查。  __iomem 是一个cookie,当编译内核是使用C=1,编译器可以通过sparse来检查使用该修饰符标记的地址是否合法,该字符定义为:

    # define __iomem        __attribute__((noderef, address_space(2)))

    目的就是在访问IO memory时需要进行严格检查,避免程序出错,如果在使用IO 内存地址时不加该参数,在编译解决会直接告警。

    其中address_space指明该地址位于那个地址空间,共划分为四个地址空间:

    v: 0 内核空间
            v: 1 用户空间
            v: 2 io存储空间
            v: 3 cpu空间

    在5.11.5版本中发现read[bwl]和write[bwl] 函数也对地址进行了__iomem修饰,该系列函数存在的类型安全问题应该已经不存在。LLD3中代码版本较老,很长时间都没有进行更新,在学习时还是需要做下代码对比。

    针对__iomem解决的整个思路,linus在https://lwn.net/Articles/102240/邮件中说的非常明白,有兴趣的同学可以了解一下,

    Big-endian大字节序接口

    针对有些端口为大字节序的问题,linux还提供了一些列的专有函数:

    unsigned int ioread16be(const void __iomem *)
    unsigned int ioread32be(const void __iomem *)
    u64 ioread64be(const void __iomem *)
    void iowrite16be(u16, void __iomem *)
    void iowrite32be(u32, void __iomem *)
    void iowrite64be(u64, void __iomem *)

    rep接口

    如果要想读或者写一系列的值到一个给定的IO memory内存地址,可以使用一下接口重复版本,相对与在使用循环调用效率要高:

    void ioread8_rep(void *addr, void *buf, unsigned long count);
    void ioread16_rep(void *addr, void *buf, unsigned long count);
    void ioread32_rep(void *addr, void *buf, unsigned long count);
    void iowrite8_rep(void *addr, const void *buf, unsigned long count);
    void iowrite16_rep(void *addr, const void *buf, unsigned long count);
    void iowrite32_rep(void *addr, const void *buf, unsigned long count);

    这些函数读或写 count 值从给定的 buf 到 给定的 addr. 注意 count 表达为在被写入的数据大小; ioread32_rep 读取 count 32-位值从 buf 开始。
     

    MMIO side effects

    在操作MMIO时,需要特别注意IO的side effects(很多地方都译为边际效应,其实字面意思不是很容易理解,其实质为副作用)。MMIO边际效率,其来源就是因为CPU缓存原因。现在CPU架构或者说是计算机的架构都是采用哈弗结构形式,即计算和存储进行分离,CPU只参与数据的计算而无法对数据或者指令进行保存,要想取得数据或者指令必须从存储中读取,而由于半导体摩尔定律的发展,CPU的运算速度每间隔2年都会有很大提升,但是对存储或者说内存的访问速率并没有太大提升,这就明显限制了整个CPU性能。为了解决IO和CPU性能之前巨大的性能差异,CPU厂商一般都会在内部集成缓存以临时存储数据,减少对外部内存的读写操作。在对普通的内存操作中,很多其实并不是从内存中读写,而是从缓存中读取(现代CPU一般都会设计多级缓存以增加性能)。而在操作IO时,如果操作IO的数据也被存储到缓存中,那么如果一旦硬件数据发生变化,此时存储在缓存中的IO数据并没有感知,进而造成程序中正在读取的IO寄存器数据和实际硬件中的IO寄存器的值并不相同,造成IO 寄存器的边际效应。

    下面可以使用一个简单的代码来说明该问题

    if (x) y = *ptr

    有一个指针为ptr,当X不为零时,会将ptr地址的值复制给y。在编译器编译之后,和下面代码是相同的代码: 

    tmp = *ptr; if (x) y = tmp

    首先将ptr地址的值赋值给tmp,然后当x不等于0时,将tmp值赋值为y。

    上述两段代码针对普通的内存操作是没有问题的,而当ptr指向IO mem时将会出现很大问题,因为此时io端口寄存器的值被存储到tmp中,而tmp值为一个缓存变量,如果在之后io端口寄存器的值发生变化,tmp并无法感知,此时就会造成y的值最终和该IO寄存器的值不一样。

    为了解决该问题,一般在定义寄存器地址时,一般使用volatile关键字修饰指向IO地址的指针,就是告诉编译器每次都从源中存取,不从缓存中存取,防止编译器进行优化从缓存中存取。

    除了上述问题之外,编译器在对代码编译时会出现过度优化,通过解析前后代码依赖顺序,将串行代码编译乱序执行,以提高效率。CPU的乱序执行也对IO mem造成很大副作用,在一个串行执行的代码中本来设计的时后读IO寄存器的值,有可能被优化成先执行读取IO寄存器的值,这样就有机会造成IO寄存器的值和实际读取出来存到变量中的值不一样,为了解决上述问题目前主要有两者手段:

    • 对硬件IO操作关闭缓存
    • 强制在代码中加入同步机制,防止乱序执行造成问题。

    linux 中提供了4个宏来来解决排序问题:

    #include <linux/kernel.h>
    void barrier(void)
    这个函数告知编译器插入一个内存屏障但是对硬件没有影响. 编译的代码将所有的
    当前改变的并且驻留在 CPU 寄存器的值存储到内存, 并且后来重新读取它们当需
    要时. 对屏障的调用阻止编译器跨越屏障的优化, 而留给硬件自由做它的重编排.
    #include <asm/system.h>
    void rmb(void);
    void read_barrier_depends(void);
    void wmb(void);
    void mb(void);
    这些函数插入硬件内存屏障在编译的指令流中; 它们的实际实例是平台相关的. 一
    个 rmb ( read memory barrier) 保证任何出现于屏障前的读在执行任何后续读之
    前完成. wmb 保证写操作中的顺序, 并且 mb 指令都保证. 每个这些指令是一个屏
    障的超集

    现在会过头看下前面的 writeb函数,里面其实也是插入了硬件同步__io_bw和__io_aw:

    static inline void writeb(u8 value, volatile void __iomem *addr)
    {
    	__io_bw();
    	__raw_writeb(value, addr);
    	__io_aw();
    }

    这也就是为什么必须使用专有函数访问IO mem的最大原因。
     

    参考资料

    https://stackoverflow.com/questions/19100536/what-is-the-use-of-iomem-in-linux-while-writing-device-drivers

    https://stackoverflow.com/questions/59113831/what-is-the-benefit-of-calling-ioread-functions-when-using-memory-mapped-io

    https://lwn.net/Articles/102232/

    https://lwn.net/Articles/102240/

    LDD3

    展开全文
  • IO端口和IO内存的区别

    2019-08-20 18:17:56
    Linux系统对IO端口和IO内存的管理 一、I/O端口 二、IO内存 三、IO端口和IO内存的区分及联系 四、外设IO端口物理地址的编址方式 统一编址 独立编址 优缺点 五、Linux下访问IO端口 I/O映射方式...

     

    1. Linux系统对IO端口和IO内存的管理
      1. 一、I/O端口
      2. 二、IO内存
      3. 三、IO端口和IO内存的区分及联系
      4. 四、外设IO端口物理地址的编址方式
        1. 统一编址
        2. 独立编址
        3. 优缺点
      5. 五、Linux下访问IO端口
        1. I/O映射方式
        2. 内存映射方式
      6. 六、Linux下访问IO内存
      7. 六、ioremap和ioport_map
      8. 七、总结

    IO端口和IO内存的区别及分别使用的函数接口 

             每个外设都是通过读写其寄存器来控制的。外设寄存器也称为I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式,可以把CPU分成两大类。一类CPU(如M68K,Power PC等)把这些寄存器看作内存的一部分,寄存器参与内存统一编址,访问寄存器就通过访问一般的内存指令进行,所以,这种CPU没有专门用于设备I/O的指令。这就是所谓的“I/O内存”方式。另一类CPU(典型的如X86),将外设的寄存器看成一个独立的地址空间,所以访问内存的指令不能用来访问这些寄存器,而要为对外设寄存器的读/写设置专用指令,如IN和OUT指令。这就是所谓的“ I/O端口”方式。但是,用于I/O指令的“地址空间”相对来说是很小的,如x86 CPU的I/O空间就只有64KB(0-0xffff)。

            结合下图,我们彻底讲述IO端口和IO内存以及内存之间的关系。主存16M字节的SDRAM,外设是个视频采集卡,上面有16M字节的SDRAM作为缓冲区。

     

    1.   CPU是i386架构的情况

     在i386系列的处理中,内存和外部IO是独立编址,也是独立寻址的。MEM的内存空间是32位可以寻址到4G,IO空间是16位可以寻址到64K。

     在Linux内核中,访问外设上的IO Port必须通过IO Port的寻址方式。而访问IO Mem就比较罗嗦,外部MEM不能和主存一样访问,虽然大小上不相上下,可是外部MEM是没有在系统中注册的。访问外部IO MEM必须通过remap映射到内核的MEM空间后才能访问。为了达到接口的同一性,内核提供了IO Port到IO Mem的映射函数。映射后IO Port就可以看作是IO Mem,按照IO Mem的访问方式即可。

    3.    CPU是ARM或PPC架构的情况

    在这一类的嵌入式处理器中,IO Port的寻址方式是采用内存映射,也就是IO bus就是Mem bus。系统的寻址能力如果是32位,IO Port+Mem(包括IO Mem)可以达到4G。

          

    1.使用I/O 端口

    I/O 端口是驱动用来和很多设备通讯的方法。

    1.1、分配I/O 端口

    在驱动还没独占设备之前,不应对端口进行操作。内核提供了一个注册接口,以允许驱动声明其需要的端口:

    #include <linux/ioport.h>
    /* request_region告诉内核:要使用first开始的n个端口。参数name为设备名。如果分配成功返回值是非NULL;否则无法使用需要的端 口(/proc/ioports包含了系统当前所有端口的分配信息,若request_region分配失败时,可以查看该文件,看谁先用了你要的端口) */
    struct resource *request_region(unsigned long first, unsigned long n, const char *name);
    /* 用完I/O端口后(可能在模块卸载时),应当调用release_region将I/O端口返还给系统。参数start和n应与之前传递给request_region一致 */
    void release_region(unsigned long start, unsigned long n); 
    /* check_region用于检查一个给定的I/O端 口集是否可用。如果给定的端口不可用,check_region返回一个错误码。不推荐使用该函数,因为即便它返回0(端口可用),它也不能保证后面的端 口分配操作会成功,因为检查和后面的端口分配并不是一个原子操作。而request_region通过加锁来保证操作的原子性,因此是安全的 */
    int check_region(unsigned long first, unsigned long n);


          1.2、操作I/O端口

    在驱动成功请求到I/O 端口后,就可以读写这些端口了。大部分硬件会将8位、16位和32位端口区分开,无法像访问内存那样混淆使用。驱动程序必须调用不同的函数来访问不同大小的端口。

    如同前面所讲的,仅支持单地址空间的计算机体系通过将I/O端口地址重新映射到内存地址来伪装端口I/O 。为了提高移植性,内核对驱动隐藏了这些细节。Linux 内核头文件(体系依赖的头文件<asm/io.h>) 定义了下列内联函数来存取I/O端口:

    /* inb/outb:读/写字节端口(8位宽)。有些体系将port参数定义为unsigned long;而有些平台则将它定义为unsigned short。inb的返回类型也是依赖体系的 */
    unsigned inb(unsigned port);
    void outb(unsigned char byte, unsigned port);
    /* inw/outw:读/写字端口(16位宽) */
    unsigned inw(unsigned port);
    void outw(unsigned short word, unsigned port);
    /* inl/outl:读/写32位端口。longword也是依赖体系的,有的体系为unsigned long;而有的为unsigned int */
    unsigned inl(unsigned port);
    void outl(unsigned longword, unsigned port);

    从现在开始,当我们使用unsigned 没有进一步指定类型时,表示是一个依赖体系的定义。

    注意,没有64位的I/O端口操作函数。即便在64位体系中,端口地址空间使用一个32位(最大)的数据通路。

    1.3、从用户空间访问I/O端口

    1.2节介绍的函数主要是提供给驱动使用,但它们也可在用户空间使用,至少在PC机上可以。GNU C 库在 <sys/io.h> 中定义它们。如果在用户空间使用这些函数,必须满足下列条件:

    1)、程序必须使用-O选项编译来强制扩展内联函数

    2)、必须使用ioperm和iopl系统调用(#include <sys/perm.h>) 来获得进行操作I/O端口的权限。ioperm 为获取单个端口的操作许可,iopl 为获取整个I/O空间许可。这2个函数都是x86特有的

    3)、程序必须以root来调用ioperm或者iopl,或者其父进程(祖先)必须以root获得的端口操作权限

    如果平台不支持ioperm和iopl系统调用,通过使用/dev/prot设备文件,用户空间仍然可以存取I/O 端口。但是要注意的是,这个文件的定义也是依赖平台的。

    1.4、字串操作

    除了一次传递一个数据的I/O操作,某些处理器实现了一次传递一序列数据(单位可以是字节、字和双字)的特殊指令。这些所谓的字串指令,它们完成任务比一个C语言循环更快。下列宏定义实现字串操作,在某些体系上,它们通过使用单个机器指令实现;但如果目标处理器没有进行字串I/O指令,则通过执行一个紧凑的循环实现。

    字串函数的原型是:

    /* insb:从I/O端口port读取count个数据(单位字节)到以内存地址addr为开始的内存空间 */
    void insb(unsigned port, void *addr, unsigned long count);
    /* outsb:将内存地址addr开始的count个数据(单位字节)写到I/O端口port */
    void outsb(unsigned port, void *addr, unsigned long count);
    /* insw:从I/O端口port读取count个数据(单位字)到以内存地址addr为开始的内存空间 */
    void insw(unsigned port, void *addr, unsigned long count);
    /* outsw:将内存地址addr开始的count个数据(单位字)写到I/O端口port */
    void outsw(unsigned port, void *addr, unsigned long count);
    /* insl:从I/O端口port读取count个数据(单位双字)到以内存地址addr为开始的内存空间 */
    void insl(unsigned port, void *addr, unsigned long count);
    /* outsl:将内存地址addr开始的count个数据(单位双字)写到I/O端口port */
    void outsl(unsigned port, void *addr, unsigned long count);

    注意:使用字串函数时,它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。使用 inw读取端口应在必要时自行转换字节序,以匹配主机字节序。

    1.5、暂停式I/O操作函数

    由于处理器的速率可能与外设(尤其是低速设备)的并不匹配,当处理器过快地传送数据到或自总线时,这时可能就会引起问题。解决方法是:如果在I/O 指令后面紧跟着另一个相似的I/O 指令,就必须插入一个小的延时。为此,Linux提供了暂停式I/O操作函数,这些函数的名子只是在非暂停式I/O操作函数(前面提到的那些I/O操作函数都是非暂停式的)名后加上_p ,如inb_p、outb_p等。大部分体系都支持这些函数,尽管它们常常被扩展为与非暂停 I/O 同样的代码,因为如果体系使用一个合理的现代外设总线,没有必要额外暂停。

    以下是ARM体系暂停式I/O宏的定义:

    #define outb_p(val,port)    outb((val),(port))
    #define outw_p(val,port)    outw((val),(port))
    #define outl_p(val,port)    outl((val),(port))
    #define inb_p(port)        inb((port))
    #define inw_p(port)        inw((port))
    #define inl_p(port)        inl((port))
    #define outsb_p(port,from,len)    outsb(port,from,len)
    #define outsw_p(port,from,len)    outsw(port,from,len)
    #define outsl_p(port,from,len)    outsl(port,from,len)
    #define insb_p(port,to,len)    insb(port,to,len)
    #define insw_p(port,to,len)    insw(port,to,len)
    #define insl_p(port,to,len)    insl(port,to,len)

    因为ARM使用内部总线,就没有必要额外暂停,所以暂停式的I/O函数被扩展为与非暂停式I/O同样的代码。

    1.6、平台依赖性

    由于自身的特性,I/O指令高度依赖于处理器,非常难以隐藏各体系间的不同。因此,大部分的关于端口 I/O的源码是平台依赖的。以下是x86和ARM所使用函数的总结:

    IA-32 (x86)

    x86_64

    这个体系支持本章介绍的所有函数;port参数的类型为unsigned short。

    ARM

    端口映射到内存,并且支持本章介绍的所有函数;port参数的类型为unsigned int;字串函数用C语言实现。

     

           2、使用 I/O 内存

    尽管 I/O 端口在x86世界中非常流行,但是用来和设备通讯的主要机制是通过内存映射的寄存器和设备内存,两者都称为I/O 内存,因为寄存器和内存之间的区别对软件是透明的。

    I/O 内存仅仅是一个类似于RAM 的区域,处理器通过总线访问该区域,以实现对设备的访问。同样,读写这个区域是有边际效应。

    根据计算机体系和总线不同,I/O 内存可分为可以或者不可以通过页表来存取。若通过页表存取,内核必须先重新编排物理地址,使其对驱动程序可见,这就意味着在进行任何I/O操作之前,你必须调用ioremap;如果不需要页表,I/O内存区域就类似于I/O端口,你可以直接使用适当的I/O函数读写它们。

    由于边际效应的缘故,不管是否需要 ioremap,都不鼓励直接使用I/O内存指针,而应使用专门的I/O内存操作函数。这些I/O内存操作函数不仅在所有平台上是安全,而且对直接使用指针操作 I/O 内存的情况进行了优化。

    2.1、I/O 内存分配和映射

    I/O 内存区在使用前必须先分配。分配内存区的函数接口在<linux/ioport.h>定义中:

    /* request_mem_region分配一个开始于start,len字节的I/O内存区。分配成功,返回一个非NULL指针;否则返回NULL。系统当前所有I/O内存分配信息都在/proc/iomem文件中列出,你分配失败时,可以看看该文件,看谁先占用了该内存区 */
    struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
    /* release_mem_region用于释放不再需要的I/O内存区 */
    void release_mem_region(unsigned long start, unsigned long len); 
    /* check_mem_region用于检查I/O内存区的可用性。同样,该函数不安全,不推荐使用 */
    int check_mem_region(unsigned long start, unsigned long len);

    在访问I/O内存之前,分配I/O内存并不是唯一要求的步骤,你还必须保证内核可存取该I/O内存。访问I/O内存并不只是简单解引用指针,在许多体系中,I/O 内存无法以这种方式直接存取。因此,还必须通过ioremap 函数设置一个映射。

    #include <asm/io.h>
    /* ioremap用于将I/O内存区映射到虚拟地址。参数phys_addr为要映射的I/O内存起始地址,参数size为要映射的I/O内存的大小,返回值为被映射到的虚拟地址 */
    void *ioremap(unsigned long phys_addr, unsigned long size);

    /* ioremap_nocache为ioremap的无缓存版本。实际上,在大部分体系中,ioremap与ioremap_nocache的实现一样的,因为所有 I/O 内存都是在无缓存的内存地址空间中 */
    void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
    /* iounmap用于释放不再需要的映射 */
    void iounmap(void * addr);

    经过 ioremap (和iounmap)之后,设备驱动就可以存取任何I/O内存地址。注意,ioremap返回的地址不可以直接解引用;相反,应当使用内核提供的访问函数。

    2.2、访问I/O内存

    访问I/O内存的正确方式是通过一系列专门用于实现此目的的函数:

    #include <asm/io.h>
    /* I/O内存读函数。参数addr应当是从ioremap获得的地址(可能包含一个整型偏移); 返回值是从给定I/O内存读取到的值 */
    unsigned int ioread8(void *addr);
    unsigned int ioread16(void *addr);
    unsigned int ioread32(void *addr);
    /* I/O内存写函数。参数addr同I/O内存读函数,参数value为要写的值 */
    void iowrite8(u8 value, void *addr);
    void iowrite16(u16 value, void *addr);
    void iowrite32(u32 value, void *addr);
    /* 以下这些函数读和写一系列值到一个给定的 I/O 内存地址,从给定的buf读或写count个值到给定的addr。参数count表示要读写的数据个数,而不是字节大小 */
    void ioread8_rep(void *addr, void *buf, unsigned long count);
    void ioread16_rep(void *addr, void *buf, unsigned long count);
    void ioread32_rep(void *addr, void *buf, unsigned long count);
    void iowrite8_rep(void *addr, const void *buf, unsigned long count);
    void iowrite16_rep(void *addr, const void *buf, unsigned long count);
    void iowrite32_rep(void *addr,,onst void *buf,,nsigned long count);
    /* 需要操作一块I/O 地址时,使用下列函数(这些函数的行为类似于它们的C库类似函数): */
    void memset_io(void *addr, u8 value, unsigned int count);
    void memcpy_fromio(void *dest, void *source, unsigned int count);
    void memcpy_toio(void *dest, void *source, unsigned int count);
    /* 旧的I/O内存读写函数,不推荐使用 */
    unsigned readb(address);
    unsigned readw(address);
    unsigned readl(address); 
    void writeb(unsigned value, address);
    void writew(unsigned value, address);
    void writel(unsigned value, address); 

    2.3、像I/O 内存一样使用端口

    一些硬件有一个有趣的特性: 有些版本使用 I/O 端口;而有些版本则使用 I/O 内存。不管是I/O 端口还是I/O 内存,处理器见到的设备寄存器都是相同的,只是访问方法不同。为了统一编程接口,使驱动程序易于编写,2.6内核提供了一个ioport_map函数:

    /* ioport_map重新映射count个I/O端口,使它们看起来I/O内存。此后,驱动程序可以在ioport_map返回的地址上使用ioread8和同类函数。这样,就可以在编程时,消除了I/O端口和I/O 内存的区别 */
    void *ioport_map(unsigned long port, unsigned int count); 
    /* ioport_unmap用于释放不再需要的映射 */
    void ioport_unmap(void *addr);

    注意,I/O 端口在重新映射前必须使用request_region分配所需的I/O 端口。


           3、ARM体系的I/O操作接口

    s3c24x0处理器使用的是I/O内存,也就是说:s3c24x0处理器使用统一编址方式,I/O寄存器和内存使用的是单一地址空间,并且读写I/O寄存器和读写内存的指令是相同的。所以推荐使用I/O内存的相关指令和函数。但这并不表示I/O端口的指令在s3c24x0中不可用。如果你注意过s3c24x0关于I/O方面的内核源码,你就会发现:其实I/O端口的指令只是一个外壳,内部还是使用和I/O内存一样的代码。

    下面是ARM体系原始的I/O操作函数。其实后面I/O端口和I/O内存操作函数,只是对这些函数进行再封装。从这里也可以看出为什么我们不推荐直接使用I/O端口和I/O内存地址指针,而是要求使用专门的I/O操作函数——专门的I/O操作函数会检查地址指针是否有效是否为IO地址(通过__iomem或__chk_io_ptr)

    #include <asm-arm/io.h>

    /*
     * Generic IO read/write. These perform native-endian accesses. Note
     * that some architectures will want to re-define __raw_{read,write}w.
     */
    extern void __raw_writesb(void __iomem *addr, const void *data, int bytelen);
    extern void __raw_writesw(void __iomem *addr, const void *data, int wordlen);
    extern void __raw_writesl(void __iomem *addr, const void *data, int longlen);
    extern void __raw_readsb(const void __iomem *addr, void *data, int bytelen);
    extern void __raw_readsw(const void __iomem *addr, void *data, int wordlen);
    extern void __raw_readsl(const void __iomem *addr, void *data, int longlen);
    #define __raw_writeb(v,a)    (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) =(v))
    #define __raw_writew(v,a)    (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) =(v))
    #define __raw_writel(v,a)    (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) =(v))
    #define __raw_readb(a)        (__chk_io_ptr(a), *(volatile unsigned char __force *)(a))
    #define __raw_readw(a)        (__chk_io_ptr(a), *(volatile unsigned short __force *)(a))
    #define __raw_readl(a)        (__chk_io_ptr(a), *(volatile unsigned int __force *)(a))

    关于__force和__iomem

    #include <linux/compiler.h>

    /* __force表示所定义的变量类型是可以做强制类型转换的 */
    #define __force __attribute__((force)) 
    /* __iomem是用来修饰一个变量的,这个变量必须是非解引用(no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是2,即设备地址映射空间。0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,2表示是设备地址映射空间 */
    #define __iomem __attribute__((noderef, address_space(2)))

    I/O端口

    #include <asm-arm/io.h>

    #define outb(v,p)        __raw_writeb(v,__io(p))
    #define outw(v,p)        __raw_writew((__force __u16) \
                        cpu_to_le16(v),__io(p))
    #define outl(v,p)        __raw_writel((__force __u32) \
                        cpu_to_le32(v),__io(p))
    #define inb(p)    ({ __u8 __v = __raw_readb(__io(p)); __v; })
    #define inw(p)    ({ __u16 __v = le16_to_cpu((__force __le16) \
                __raw_readw(__io(p))); __v; })
    #define inl(p)    ({ __u32 __v = le32_to_cpu((__force __le32) \
                __raw_readl(__io(p))); __v; })
    #define outsb(p,d,l)        __raw_writesb(__io(p),d,l)
    #define outsw(p,d,l)        __raw_writesw(__io(p),d,l)
    #define outsl(p,d,l)        __raw_writesl(__io(p),d,l)
    #define insb(p,d,l)        __raw_readsb(__io(p),d,l)
    #define insw(p,d,l)        __raw_readsw(__io(p),d,l)
    #define insl(p,d,l)        __raw_readsl(__io(p),d,l)

    I/O内存

    #include <asm-arm/io.h>

    #define ioread8(p)    ({ unsigned int __v = __raw_readb(p); __v; })
    #define ioread16(p)    ({ unsigned int __v = le16_to_cpu((__force __le16)__raw_readw(p));__v; })
    #define ioread32(p)    ({ unsigned int __v = le32_to_cpu((__force __le32)__raw_readl(p));__v; })
    #define iowrite8(v,p)    __raw_writeb(v, p)
    #define iowrite16(v,p)    __raw_writew((__force __u16)cpu_to_le16(v), p)
    #define iowrite32(v,p)    __raw_writel((__force __u32)cpu_to_le32(v), p)
    #define ioread8_rep(p,d,c)    __raw_readsb(p,d,c)
    #define ioread16_rep(p,d,c)    __raw_readsw(p,d,c)
    #define ioread32_rep(p,d,c)    __raw_readsl(p,d,c)
    #define iowrite8_rep(p,s,c)    __raw_writesb(p,s,c)
    #define iowrite16_rep(p,s,c)    __raw_writesw(p,s,c)
    #define iowrite32_rep(p,s,c)    __raw_writesl(p,s,c)

    注意:

    1)、所有的读写指令(I/O操作函数)所赋的地址必须都是虚拟地址,你有两种选择:使用内核已经定义好的地址,如在include/asm-arm/arch-s3c2410/regs-xxx.h中定义了s3c2410处理器各外设寄存器地址(其他处理器芯片也可在类似路径找到内核定义好的外设寄存器的虚拟地址;另一种方法就是使用自己用ioremap映射的虚拟地址。绝对不能使用实际的物理地址,否则会因为内核无法处理地址而出现oops。

    2)、在使用I/O指令时,可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因为request的功能只是告诉内核端口被谁占用了,如再次request,内核会制止(资源busy)。但是不推荐这么做,这样的代码也不规范,可能会引起并发问题(很多时候我们都需要独占设备)。

    3)、在使用I/O指令时,所赋的地址数据有时必须通过强制类型转换为 unsigned long,不然会有警告。

    4)、在include\asm-arm\arch-s3c2410\hardware.h中定义了很多io口的操作函数,有需要可以在驱动中直接使用,很方便。

     

     

     

     

     

     

    Linux系统对IO端口和IO内存的管理

    http://blog.csdn.net/ce123/article/details/7204458

    一、I/O端口

          端口(port)是接口电路中能被CPU直接访问的寄存器的地址。几乎每一种外设都是通过读写设备上的寄存器来进行的。CPU通过这些地址即端口向接口电 路中的寄存器发送命令,读取状态和传送数据。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存 器通常被连续地编址。

    二、IO内存

           例如,在PC上可以插上一块图形卡,有2MB的存储空间,甚至可能还带有ROM,其中装有可执行代码。

     

    三、IO端口和IO内存的区分及联系

             这两者如何区分就涉及到硬件知识,X86体系中,具有两个地址空间:IO空间和内存空间,而RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,即内存空间。
    内存空间:内存地址寻址范围,32位操作系统内存空间为2的32次幂,即4G。
    IO空间:X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间。

    IO端口:当寄存器或内存位于IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或者说I/O ports,这个I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。

    IO内存:当寄存器或内存位于内存空间时,称为IO内存。

     

    四、外设IO端口物理地址的编址方式

            CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。

    1、统一编址

      RISC指令系统的CPU(如,PowerPC、m68k、ARM等)通常只实现一个物 理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个 内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

           统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,外设的寄存器和内存统称“I/O空间”)。

    2、独立编址

            而另外一些体系结构的CPU(典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地址空间”或者“I/O端口空间”。这是一个与CPU 地RAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访 问这一空间中的地址单元(也即I/O端口)。与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间就只有64KB(0-0xffff)。这是“I/O映射方式”的一个主要缺点。

            独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”。

    3、优缺点

    独立编址主要优点是:
    1)、I/O端口地址不占用存储器空间;使用专门的I/O指令对端口进行操作,I/O指令短,执行速度快。
    2)、并且由于专门I/O指令与存储器访问指令有明显的区别,使程序中I/O操作和存储器操作层次清晰,程序的可读性强。
    3)、同时,由于使用专门的I/O指令访问端口,并且I/O端口地址和存储器地址是分开的,故I/O端口地址和存储器地址可以重叠,而不会相互混淆。 
    4)、译码电路比较简单(因为I/0端口的地址空间一般较小,所用地址线也就较少)。
    其缺点是:只能用专门的I/0指令,访问端口的方法不如访问存储器的方法多。

    统一编址优点:
    1)、由于对I/O设备的访问是使用访问存储器的指令,所以指令类型多,功能齐全,这不仅使访问I/O端口可实现输入/输出操作,而且还可对端口内容进行算术逻辑运算,移位等等;
    2)、另外,能给端口有较大的编址空间,这对大型控制系统和数据通信系统是很有意义的。
    这种方式的缺点是端口占用了存储器的地址空间,使存储器容量减小,另外指令长度比专门I/O指令要长,因而执行速度较慢。
            究竟采用哪一种取决于系统的总体设计。在一个系统中也可以同时使用两种方式,前提是首先要支持I/O独立编址。Intel的x86微处理器都支持I/O 独立编址,因为它们的指令系统中都有I/O指令,并设置了可以区分I/O访问和存储器访问的控制信号引脚。而一些微处理器或单片机,为了减少引脚,从而减 少芯片占用面积,不支持I/O独立编址,只能采用存储器统一编址。

    五、Linux下访问IO端口

               对于某一既定的系统,它要么是独立编址、要么是统一编址,具体采用哪一种则取决于CPU的体系结构。 如,PowerPC、m68k等采用统一编址,而X86等则采用独立编址,存在IO空间的概念。目前,大多数嵌入式微控制器如ARM、PowerPC等并 不提供I/O空间,仅有内存空间,可直接用地址、指针访问。但对于Linux内核而言,它可能用于不同的CPU,所以它必须都要考虑这两种方式,于是它采 用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region),不论你采用哪种方式,都要先申请IO区域:request_resource(),结束时释放 它:release_resource()。

    IO region是一种IO资源,因此它可以用resource结构类型来描述。

             访问IO端口有2种途径:I/O映射方式(I/O-mapped)、内存映射方式(Memory-mapped)。前一种途径不映射到内存空间,直接使用 intb()/outb()之类的函数来读写IO端口;后一种MMIO是先把IO端口映射到IO内存(“内存空间”),再使用访问IO内存的函数来访问 IO端口。

    1、I/O映射方式

           直接使用IO端口操作函数:在设备打开或驱动模块被加载时申请IO端口区域,之后使用inb(),outb()等进行端口访问,最后在设备关闭或驱动被卸载时释放IO端口范围。

     in、out、ins和outs汇编语言指令都可以访问I/O端口。内核中包含了以下辅助函数来简化这种访问:

    inb( )、inw( )、inl( )
    分别从I/O端口读取1、2或4个连续字节。后缀“b”、“w”、“l”分别代表一个字节(8位)、一个字(16位)以及一个长整型(32位)。

    inb_p( )、inw_p( )、inl_p( )
    分别从I/O端口读取1、2或4个连续字节,然后执行一条“哑元(dummy,即空指令)”指令使CPU暂停。

    outb( )、outw( )、outl( )
    分别向一个I/O端口写入1、2或4个连续字节。

    outb_p( )、outw_p( )、outl_p( )
    分别向一个I/O端口写入1、2或4个连续字节,然后执行一条“哑元”指令使CPU暂停。

    insb( )、insw( )、insl( )
    分别从I/O端口读入以1、2或4个字节为一组的连续字节序列。字节序列的长度由该函数的参数给出。

    outsb( )、outsw( )、outsl( )
    分别向I/O端口写入以1、2或4个字节为一组的连续字节序列。

    流程如下:

     

          虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了,对基于ISA总线的系统来说更是如此。通常,I/O设备驱 动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;但是,如果其他硬件设备已经使用这个端口,那么系统就会崩溃。为了防止这种情况的发生,内 核必须使用“资源”来记录分配给每个硬件设备的I/O端口。资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。在这里,资源表示I/O端口地 址的一个范围。每个资源对应的信息存放在resource数据结构中:

    [plain] view plaincopy

     

    1. struct resource {  
    2.          resource_size_t start;// 资源范围的开始  
    3.          resource_size_t end;// 资源范围的结束  
    4.          const char *name; //资源拥有者的名字  
    5.          unsigned long flags;// 各种标志  
    6.          struct resource *parent, *sibling, *child;// 指向资源树中父亲,兄弟和孩子的指针  
    7. };  


              所有的同种资源都插入到一个树型数据结构(父亲、兄弟和孩子)中;例如,表示I/O端口地址范围的所有资源都包括在一个根节点为 ioport_resource的树中。节点的孩子被收集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。

            为什么使用树?例如,考虑一下IDE硬盘接口所使用的I/O端口地址-比如说从0xf000 到 0xf00f。那么,start字段为0xf000 且end 字段为0xf00f的这样一个资源包含在树中,控制器的常规名字存放在name字段中。但是,IDE设备驱动程序需要记住另外的信息,也就是IDE链主盘 使用0xf000 到0xf007的子范围,从盘使用0xf008 到0xf00f的子范围。为了做到这点,设备驱动程序把两个子范围对应的孩子插入到从0xf000 到0xf00f的整个范围对应的资源下。一般来说,树中的每个节点肯定相当于父节点对应范围的一个子范围。I/O端口资源树 (ioport_resource)的根节点跨越了整个I/O地址空间(从端口0到65535)。

    任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:

    request_resource( )        //把一个给定范围分配给一个I/O设备。

    allocate_resource( )        //在资源树中寻找一个给定大小和排列方式的可用范围;若存在,将这个范围分配给一个I/O设备(主要由PCI设备驱动程序使用,可以使用任意的端口号和主板上的内存地址对其进行配置)。

    release_resource( )      //释放以前分配给I/O设备的给定范围。

    内核也为以上函数定义了一些应用于I/O端口的快捷函数:request_region( )分配I/O端口的给定范围,release_region( )释放以前分配给I/O端口的范围。当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中获得。

    2、内存映射方式

             将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射。

    映射函数的原型为:
    void *ioport_map(unsigned long port, unsigned int count);
    通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。但请注意,在进行映射前,还必须通过request_region( )分配I/O端口。

    当不再需要这种映射时,需要调用下面的函数来撤消:
    void ioport_unmap(void *addr);

    在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是宜使用Linux内核的如下一组函数来完成访问I/O内存:·读I/O内存
    unsigned int ioread8(void *addr);
    unsigned int ioread16(void *addr);
    unsigned int ioread32(void *addr);
    与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
    unsigned readb(address);
    unsigned readw(address);
    unsigned readl(address);
    ·写I/O内存
    void iowrite8(u8 value, void *addr);
    void iowrite16(u16 value, void *addr);
    void iowrite32(u32 value, void *addr);
    与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
    void writeb(unsigned value, address);
    void writew(unsigned value, address);
    void writel(unsigned value, address);

    流程如下:

     

    六、Linux下访问IO内存

             IO内存的访问方法是:首先调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址, 之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放 release_mem_region()申请的IO内存资源。

    struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
       这个函数从内核申请len个内存地址(在3G~4G之间的虚地址),而这里的start为I/O物理地址,name为设备的名称。注意,。如果分配成功,则返回非NULL,否则,返回NULL。
    另外,可以通过/proc/iomem查看系统给各种设备的内存范围。

    要释放所申请的I/O内存,应当使用release_mem_region()函数:
    void release_mem_region(unsigned long start, unsigned long len)

    申请一组I/O内存后, 调用ioremap()函数:
    void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
    其中三个参数的含义为:
    phys_addr:与requset_mem_region函数中参数start相同的I/O物理地址;
    size:要映射的空间的大小;
    flags:要映射的IO空间的和权限有关的标志;

    功能:将一个I/O地址空间映射到内核的虚拟地址空间上(通过release_mem_region()申请到的)

    流程如下:

     

    六、ioremap和ioport_map

    下面具体看一下ioport_map和ioport_umap的源码:

    [plain] view plaincopy

     

    1. void __iomem *ioport_map(unsigned long port, unsigned int nr)  
    2. {  
    3.     if (port > PIO_MASK)  
    4.         return NULL;  
    5.     return (void __iomem *) (unsigned long) (port + PIO_OFFSET);  
    6. }  
    7.   
    8. void ioport_unmap(void __iomem *addr)  
    9. {  
    10.     /* Nothing to do */  
    11. }  

              ioport_map仅仅是将port加上PIO_OFFSET(64k),而ioport_unmap则什么都不做。这样portio的64k空间就被映射到虚拟地址的64k~128k之间,而ioremap返回的虚拟地址则肯定在3G之上。ioport_map函数的目的是试图提供与ioremap一致的虚拟地址空间。分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为了让工程师可使用统一的I/O内存访问接口ioread8/iowrite8(......)访问I/O端口。
              最后来看一下ioread8的源码,其实现也就是对虚拟地址进行了判断,以区分IO端口和IO内存,然后分别使用inb/outb和readb/writeb来读写。

    [plain] view plaincopy

     

    1. unsigned int fastcall ioread8(void __iomem *addr)  
    2. {  
    3.     IO_COND(addr, return inb(port), return readb(addr));  
    4. }  
    5.   
    6. #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)  
    7. #define IO_COND(addr, is_pio, is_mmio) do { \  
    8.     unsigned long port = (unsigned long __force)addr; \  
    9.         if (port < PIO_RESERVED) { \  
    10.             VERIFY_PIO(port); \  
    11.             port &= PIO_MASK; \  
    12.             is_pio; \  
    13.         } else { \  
    14.             is_mmio; \  
    15.         } \  
    16. } while (0)  
    17.   
    18. 展开:  
    19. unsigned int fastcall ioread8(void __iomem *addr)  
    20. {  
    21.     unsigned long port = (unsigned long __force)addr;  
    22.     if( port < 0x40000UL ) {  
    23.         BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );  
    24.         port &= PIO_MASK;  
    25.         return inb(port);  
    26.     }else{  
    27.         return readb(addr);  
    28.     }  
    29. }  

    七、总结

             外设IO寄存器地址独立编址的CPU,这时应该称外设IO寄存器为IO端 口,访问IO寄存器可通过ioport_map将其映射到虚拟地址空间,但实际上这是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为 了可以使用和IO内存一样的接口访问IO寄存器;也可以直接使用in/out指令访问IO寄存器。

              例如:Intel x86平台普通使用了名为内存映射(MMIO)的技术,该技术是PCI规范的一部分,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访 问内存一样。

              外设IO寄存器地址统一编址的CPU,这时应该称外设IO寄存器为IO内存,访问IO寄存器可通过ioremap将其映射到虚拟地址空间,然后再使用read/write接口访问。

    展开全文
  • 问题开始于 在网上看到DNS会使用UDP和TCP的43号端口,一时没理解TCP和UDP两种协议与端口的关系。 http://bbs.51cto.com/thread-1010537-1.html DNS的端口有TCP,UDP 53端口,那么什么时候用TCP,什么时候用UDP的端口...

    问题开始于 在网上看到DNS会使用UDP和TCP的43号端口,一时没理解TCP和UDP两种协议与端口的关系。  端口只是传输层的概念。传输层协议除了tcp udp 还有其他的如netbios,spx等。网络层也不只是IP协议还有ipx等。与IP协议配套使用实现其功能的还有地址解析协议ARP、逆地址解析协议RARP、因特网报文协议ICMP(ping命令用到它)、因特网组管理协议IGMP。

    维基百科   https://en.wikipedia.org/wiki/Port_(computer_networking)

    In the internet protocol suite, a port is an endpoint of communication in anoperating system. While the term is also used for hardware devices, in software it is a logical construct that identifies a specificprocess or a type of network service.

    A port is always associated with an IP address of a host and the protocol type of the communication, and thus completes the destination or origination address of a communication session. A port is identified for each address and protocol by a 16-bit number, commonly known as theport number.

    Specific port numbers are often used to identify specific services. Of the thousands of enumerated ports, 1024well-known port numbers are reserved by convention to identify specific service types on a host. In theclient–server model of application architecture, the ports that network clients connect to for service initiation provide amultiplexing service. After initial communication binds to the well-known port number, this port is freed by switching each instance of service requests to a dedicated, connection-specific port number, so that additional clients can be serviced. The protocols that primarily use ports are the transport layer protocols, such as the Transmission Control Protocol (TCP) and the User Datagram Protocol (UDP).专用端口和用于专用的服务,65535个端口中,1024个是保留的用语特殊服务的。在c/s模式的应用框架中,网络客户端为了初始化服务连接到 的端口提供多路复用服务。在初始通讯连接到well-known端口后,通过把每一个服务请求切换到专用的指定的端口,这个端口就可以被释放,其他客户端就可以被服务了。使用端口的协议主要是传输层协议比如tcp,udp.

    Ports were unnecessary on direct point-to-point links when the computers at each end could only run one program at a time. Ports became necessary after computers became capable ofexecuting more than one program at a time and were connected to modern packet-switched networks. 端口是在电脑进化到能同时处理多个线程和接入到分组交换网后边的很有必要。

    Details

    Transport layer protocols, such as theTransmission Control Protocol (TCP) and the User Datagram Protocol (UDP), specify a source and destination port number in theirheaders. A port number is a 16-bit unsigned integer, thus ranging from 0 to 65535.[a] A process associates its input or output channels, via an Internet socket (a type of file descriptor), with a transport protocol, a port number, and an IP address. This process is known as binding, and enables sending and receiving data via the network. Theoperating system's networking software has the task of transmitting outgoing data from all application ports onto the network, and forwarding arriving network packets to processes by matching the packet's IP address and port number. Only one process may bind to a specific IP address and port combination using the same transport protocol. Common application failures, sometimes calledport conflicts, occur when multiple programs attempt to bind to the same port numbers on the same IP address using the same protocol.传输层协议如tcp,udp,在他们的协议头中指定源端口和目的端口。端口号是16bit的无符号整型数0~65535。进程关联其输入输出通道,通过网络socket,with传输协议,端口号和IP地址。这个过程称为绑定,并且可以通过网络发送和接收数据。操作系统的网络软件的任务包括传输从各个应用端口向外发送到网络的数据,并通过匹配数据包的IP地址和端口号转发到达的(arriving)网络数据包。只有一个进程可以绑定到一个specific IP地址和端口组合,使用相同的传输协议。常见的应用错误,比如端口冲突,发生在多个程序使用相同的协议尝试绑定同一个IP地址和端口。


    Applications implementing common services often use specifically reserved well-known port numbers for receiving service requests from clients. This process is known aslistening, and involves the receipt of a request on the well-known port and establishing a one-to-one server-client dialog, using the same local port number. Other clients may continue to connect to the listening port; this works because a TCP connection is identified by a tuple consisting of the local address, the local port, the remote address, and the remote port.[1] The well-known ports are defined by convention overseen by the Internet Assigned Numbers Authority (IANA). The core network services, such as theWorld-Wide Web, typically use well-known port numbers. In many operating systems special privileges are required for applications to bind to these ports, because these are often deemed critical to the operation of IP networks. Conversely, the client end of a connection typically uses a high port number allocated for short term use, therefore called anephemeral port.实现公共服务的应用程序通常使用专门保留的知名端口号来接收来自客户端的服务请求。这个过程被称为侦听,并涉及在已知端口上接收请求并使用同一本地端口号建立一对一的服务器-客户端对话。其他客户可以继续连接到监听端口;这是因为TCP连接是  由本地地址、本地端口、远程地址和远程端口组成的元组(tuple)  来标识的。well-known端口是有IANA监督定义的。重要的网络服务比如www,就使用well-known端口。在许多操作系统中,应用想要绑定到这些端口需要特殊的权限,因为这些well-known端口被认为是对IP网操作非常重要的。相反的,连接的客户端一般使用分配给短期使用的较大端口号,所以称为临时端口。

    The port numbers are encoded in the transport protocol packet header, and they can be readily interpreted not only by the sending and receiving computers, but also by other components of the networking infrastructure. In particular,firewalls are commonly configured to differentiate between packets based on their source or destination port numbers.Port forwarding is an example application of this.端口号被编码在传输协议的包头,并且可以方便的接受和发送的计算机以及网络基础设施中的其他组成部分。特别的,防火墙通常被配置为能够根据源端口或目的端口号来区分数据包。

    The practice of attempting to connect to a range of ports in sequence on a single computer is commonly known asport scanning. This is usually associated either with malicious cracking attempts or with network administrators looking for possible vulnerabilities to help prevent such attacks. Port connection attempts are frequently monitored and logged by computers. The technique ofport knocking uses a series of port connections (knocks) from a client computer to enable a server connection.

    Examples

    An example for the use of ports is the Internet mail system. A server used for sending and receiving email generally needs two services. The first service is used to transport email to and from other servers. This is accomplished with theSimple Mail Transfer Protocol (SMTP). The SMTP service application usually listens on TCP port 25 for incoming requests. The second service is usually either thePost Office Protocol (POP) or the Internet Message Access Protocol (IMAP) which is used by e-mail client applications on users' personal computers to fetch email messages from the server. The POP service listens on TCP port number 110. Both services may be running on the same host computer, in which case the port number distinguishes the service that was requested by a remote computer, be it a user's computer or another mail server.

    While the listening port number of a server is well defined (IANA calls these the well-known ports), the client's port number is often chosen from the dynamic port range (see below). In some applications, the clients and the server each use specific port numbers assigned by the IANA. A good example of this isDHCP in which the client always uses UDP port 68 and the server always uses UDP port 67.


    http://bbs.51cto.com/thread-1010537-1.html

    DNS的端口有TCP,UDP  53端口,那么什么时候用TCP,什么时候用UDP的端口的呢?

    DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算个另类。但很少有人知道DNS分别在什么情况下使用这两种协议。
    先简单介绍下TCP与UDP。
        TCP是一种面向连接的协议,提供可靠的数据传输,一般服务质量要求比较高的情况,使用这个协议。UDP---用户数据报协议,是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
    TCP与UDP的区别:
        UDP和TCP协议的主要区别是两者在如何实现信息的可靠传递方面不同。TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。与TCP不同,UDP协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP协议称为不可靠的传输协议。相对于TCP协议,UDP协议的另外一个不同之处在于如何接收突发性的多个数据报。不同于TCP,UDP并不能确保数据的发送和接收顺序。事实上,UDP协议的这种乱序性基本上很少出现,通常只会在网络非常拥挤的情况下才有可能发生。

        既然UDP是一种不可靠的网络协议,那么还有什么使用价值或必要呢?其实不然,在有些情况下UDP协议可能会变得非常有用。因为UDP具有TCP所望尘莫及的速度优势。虽然TCP协议中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销,无疑使速度受到严重的影响。反观UDP由于排除了信息可靠传递机制,将安全和排序等功能移交给上层应用来完成,极大降低了执行时间,使速度得到了保证。

    DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议;

        DNS的规范规定了2种类型的DNS服务器,一个叫主DNS服务器,一个叫辅助DNS服务器。在一个区中 主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区传送(zone transfer)。

    为什么既使用TCP又使用UDP?
    首先了解一下TCP与UDP传送字节的长度限制:
       UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。
    区域传送时使用TCP,主要有一下两点考虑:
    1.辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多。
    2.TCP是一种可靠的连接,保证了数据的准确性。
    域名解析时使用UDP协议:
    客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。


    如果用wiresharksniffer或古老些的tcpdump抓包分析,会发现几乎所有的情况都是在使用UDP,使用TCP的情况非常罕见,神秘兮兮。其实当解析器发出一个request后,返回的response中的tc删节标志比特位被置1时,说明反馈报文因为超长而有删节。这是因为UDP的报文最大长度为512字节。解析器发现后,将使用TCP重发requestTCP允许报文长度超过512字节。既然TCP能将data stream分成多个segment,它就能用更多的segment来传送任意长度的数据。
         
    另外一种情况是,当一个域的辅助域名服务器启动时,将从该域的主域名服务器primary DNS server执行区域传送。除此之外,辅域名服务器也会定时(一般时3小时)向PDS进行查询以便了解SOA的数据是否有变动。如有变动,也会执行一次区域传送。区域传送将使用TCP而不是UDP,因为传送的数据量比一个requestresponse多得多。
         DNS
    主要还是使用UDP,解析器还是服务端都必须自己处理重传和超时。DNS往往需要跨越广域网或互联网,分组丢失率和往返时间的不确定性要更大些,这对于DNS客户端来说是个考验,好的重传和超时检测就显得更重要了。

    http://forum.h3c.com/thread-95132-1-1.html   说说 IP协议号 和TCP 端口号,UDP端口号区别

    协议号和端口号的区别:网络层-数据包的包格式里面有个很重要的字段叫做协议号。比如在传输层如果是tcp连接,那么在网络层ip包里面的协议号就将会有个值是6,如果是udp的话那个值就是17,协议号1代表ICMP,协议号2代表IGMP,GRE 47等等---传输层传输层--通过接口关联(端口的字段叫做端口)---应用层,详见RFC 1700      协议号是存在于IP数据报的首部的20字节的固定部分,占有8bit.该字段是指出此数据报所携带的是数据是使用何种协议,以便目的主机的IP层知道将数据部分上交给哪个处理过程。也就是协议字段告诉IP层应当如何交付数据。

          而端口,则是运输层服务访问点TSAP{(TransportService Access Point)传输服务访问点,在计算机网络当中,传输层要在用户之间提供可靠和有效的端-端(如TSAP源端 ->  TSAP目的端的传输选择)服务,必须把一个用户进程和其他的用户进程区分开,主要由传输地址来实现。

    (目标用户需要这样的说明:用户标识、传输实体、主机地址和网络号码。)

    这时,传输层需要定义一组传输地址,以供通信选用。传输地址用传输服务访问点TSAP)来描述。

    (为确保所有的传输地址在整个网络中是唯一的,传输地址规定由网络号、主机号以及主机分配的端口组成。)在层次地址构成中,一个实际的例子就是在Internet<IP地址><端口号>表示TSAP

    比如在TelnetTSAP是中IP地址,端口23)应用层的进程始终处于监听状态,其属于静态分配TSAP}

    端口的作用是让应用层的各种应用进程都能将其数据通过端口向下交付给运输层,以及让运输层知道应当将其报文段中的数据向上通过端口交付给应用层的进程。

    端口号存在于UDP和TCP报文的首部,而IP数据报则是将UDP或者TCP报文做为其数据部分,再加上IP数据报首部,封装成IP数据报。而协议号则是存在这个IP数据报的首部.

          比如,客户端发送一个数据包给ip,然后ip将进来的数据发送给传输协议(tcp或者udp),然后传输协议再根据数据包的第一个报头中的协议号和端口号来决定将此数据包给哪个应用程序(也叫网络服务)。也就是说,协议号+端口号 唯一的确定了接收数据包的网络进程。由于标志数据发送进程的'源端口号'和标志数据接受进程的'目的端口号'都包含在每个tcp段和udp段的第一个分组中,系统可以知道到底是哪个客户应用程序同哪个服务器应用程序在通讯,而不会将数据发送到别的进程中。

           但是要注意的一点是同样的一个端口在不同的协议中的意义是不同的,比如tcp和udp中的端口31指的并不是同一个端口。但是对于同一个协议,端口号确实唯一的。

           在端口中分为两种,一是'知名端口',也即小于256的端口号。另一种是'动态分配的端口',也就是在需要时再将其赋给特定的进程。这类似于nt服务器或者163拨号上网,也就是动态的分配给用户一个目前没有用到的标志。动态分配的端口号都是高于标准端口号范围的。网络服务常用的应用协议和对应的标准端口号

    https://www.zhihu.com/question/20583641         车小胖计算机网络话题优秀回答者 网络主治大夫,专治疑难…

    先来假设没有TCP,甚至没有IP层,只有MAC对应的数据链路层,HTTP等协议能跑多远?
    直接把HTTP封装在Ethernet Frame 里,可以吗?当然可以,在同一个二层广播域里,通过MAC地址来识别对方,然后HTTP的数据通过网卡接口函数完成发送和接收。

    那问题来了,如何保证数据万无一失地到达对方?让网卡来保证数据的可靠传输吗?网卡只对Ethernet 帧头做解释,以太网头14个字节也没有哪个字段可以胜任这个可靠传输的任务,那HTTP是不是要自己实现数据传输得可靠机制,比如发送一段1000字节的数据,要等待对方给自己确认收到,然后再来发送,这样是可以保证数据可靠传输。

    同样FTP,STMP,POP3,BGP都是要保证数据可靠的传输,那他们是不是也要自己实现这些可靠的机制呢?那是一定,也就是说只要有一个应用,要可靠传输,必须由 application protocol 来实现!是不是很笨啊???这所有application protocol 的可靠传输机制是同样的实现,代码都应该类似,如果我们把这些被重用的代码封装起来,即接口函数API,让他实现数据的可靠传输,同时用一个标识符来表明这是哪个application protocol,是不是可行呢?可行!!!

    这就是伟大TCP/IP协议的缔造者所高度抽象出来的东东:TCP!它把可靠传输机制代码封装成了接口函数API,即socket , 同时用TCP Port来辨别其服务的application Protocol。而application protocol 只需要对自己的协议本身和协议数据做解释,完成端对端的会话。

    第二个问题:现在Ethernet + TCP + application protocol ,这个包能跑多远?也就是一个广播域那么大的范围了吧?yes,sir,小样再怎么得瑟也逃不出广播域的圈圈。IP层就是来解决这个问题的,有了IP层可以让Internet 成为可能,一句广告词:impossible is nothing! 要知道IP就是 Internet Protocol 的缩写。

    那你可能要问了, 我想要IP层,不想要TCP层,直接把数据封装在IP层可以吗? 太可以了!不光可以,而且好多协议也是这么做的,OSPF,EIGRP,GRE,ESP,AH等协议都是这么做的,如果他们想保证可靠传输,他们自己用代码来实现,这当然可以。

    问题是IP只用一个字节来表示协议号,理论上只能辨识255种上层协议,资源非常紧张,而且都被知名的大牌协议所霸占着,比如TCPICMPIGMP,包括上面提到的,哪里轮的上你哦!为了解决这个资源紧张问题,于是就有了另外一个什么鸟事不干,只用来辨别application protocol 的小东西:UDP!

    其实UDP除了提供一个Port来分辨application protocol, 确实没干点别的,但是Port号占用2个字节,理论上可以分辨65535 application protocol,就这一条就可以让其功德圆满了,正是因为它不像TCP,是完全无状态协议,所以也深得一些应用程序的青睐,因为UDP无状态,IP也无状态,会话所有的状态都由application protocol 来进行控制,这也是一种选择。

    另外UDP-based 的应用可以实现可靠传输,比如TFTP,那就由TFTP自己来实现可靠传输;也可以把数据交给UDP,让其发送出去即可,对发送出去的数据不需要确认,同学们会问:这是哪种应用啊?语音流量,丢了就丢了,对方听不见大不了再说一遍。

    综上,TCP提供一种可靠传输机制,有状态;UDP提供一种更多的空间来辨别上层的协议,无状态

    百度百科:

    运输协议

    运输层(传输层),解决的是计算机程序到计算机程序之间的通信问题,即所谓的“端”到

    “端”的通信。引入传输层的原因: 增加复用和分用的功能、 消除网络层的不可靠性、 提供从源端主机到目的端主机的可靠的、与实际使用的网络无关的信息传输。运输层是ISO/OSI的第四层,处于通信子网资源子网之间,是整个协议层次中最核心的一层。它的作用是在优化网络服务的基础上,为源主机和目标主机之间提供可靠的价格合理的透明数据传输,使高层服务用户在相互通信时不必关心通信子网实现的细节。运输层的最终目标是为传送服务用户提供有效、可靠和价格合理的运输服务,而传送服务的用户即会话层实体。运输层是OSI七层模型中最重要最关键的一层,是唯一负责总体数据传输和控制的一层。运输层要达到两个主要目的:第一提供可靠的端到端的通信;第二,向会话层提供独立于网络的运输服务。 首先,运输层之上的会话层、表示层应用层均不包含任何数据传输的功能,而网络层又不一定需要保证发送站的数据可靠地送至目的站;其次会话层不必考虑实际网络的结构、属性、连接方式等实现的细节。根据运输层在七层模型中的目的和地位,它的主要功能是对一个进行的对话或连接提供可靠的传输服务;在通向网络的单一物理连接上实现该连接的利用复用;在单一连接上进行端到端的序号及流量控制;进行端到端的差错控制及恢复;提供运输层的其它服务等。运输层反映并扩展了网络层子系统的服务功能,并通过运输层地址提供给高层用户传输数据的通信端口,使系统间高层资源的共享不必考虑数据通信方面的问题。

    基本功能

    提供端到端(进程-进程)的可靠通信,即向高层用户屏蔽通信子网的细节,提供

    通用的传输接口。

    1. 把传输地址映射网络地址;2. 把端到端的传输连接复用到网络连接上;3. 传输连接管理;4. 端到端的顺序控制、差错检测及恢复、分段处理及QoS监测;5. 加速数据传送;6.将传输层的传输地址映射到网络层的网络地址;7. 将多路的端点到端点的传输连接变成一路网络连接;8. 传输连接的建立、释放监控;9. 完成传输服务数据单元的传送;10. 端点到端点传输时的差错检验及对服务质量的监督。

    TCP/IP的运输层中的两个协议

    用户数据报协议UDP(UserDatagram Protocol):提供无连接服务;传输控制协议TCP(TransmissionControl Protocol):提供面向连接服务。UDP和TCP都使用IP协议。

    1.UDP提供了不可靠的无连接传输服务。它使用IP携带报文,但增加了对给定主机上多个目标进行区别的能力。 UDP没有确认机制;不对报文排序;没有超时机制;没有反馈机制控制流量;使用UDP的应用程序要承担可靠性方面的全部工作。

    2. 传输控制协议TCP(Tranmission Control Protocol)面向连接的、可靠的、端到端的、基于字节流的传输协议;TCP不支持多播(multicast)和广播(broadcast);TCP连接是基于字节流的,而非消息流,消息的边界在端到端的传输中不能得到保留;对于应用程序发来的数据,TCP可以立即发送,也可以缓存

    一段时间以便一次发送更多的数据。为了强迫数据发送,可以使用PUSH标记;对于紧急数据(urgentdata),可以使用URGENT标记。

    端口的概念

    端口:用16位来表示,即一个主机共有65536个端口。序号小于256的端口称为通用端口,如FTP21端口,WWW是80端口等。端口用来标识一个服务或应用。一台主机可以同时提供多个服务和建立多个连接。端口(port)就是传输层应用程序接口应用层的各个进程是通过相应的端口才能与运输实体进行交互。服务器一般都是通过人们所熟知的端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传输协议)服务器的UDP端口号都是69。任何TCP/IP实现所提供的服务都用众所周知的1-1023之间的端口号。这些人们所熟知的端口号由Internet端口号分配机构(Internet Assigned Numbers Authority, IANA)来管理。

    用户数据报UDP的格式

    用户数据报UDP包括两个字段数据字段和首部字段。首部字段有8个字节,4个字段组成,每个字段两个字节。源端口字段: 源端口号;目的端口字段:目的端口号;长度字段:UDP数据报的长度;检验和字段:防止UDP数据报在传输中出错。伪首部:仅为计算检验和而构造。UDP通常作为IP的一个简单

    扩展。它引入了一个进程端口的匹配机制,使得某用户进程发送的每个UDP报文都包含有报文目的端口的编号和报文源端口的编号,从而使UDP软件可以把报文传递给正确的接收进程。

    UDP提供的服务:UDP提供的服务与IP协议一样,是不可靠的、无连接的服务。但它又不同于IP协议,因为IP协议是网络层协议向运输层提供无连接的服务,而UDP是传输层协议,它向应用层提供无连接的服务。UDP有以下优点:发送数据之前不需要建立连接,发送后也无需释放,因此,减少了开销和发送数据的时延。UDP不使用拥塞控制,也不保证可靠交付,因此,主机不需要维护有许多参数的连接状态表。UDP用户数据报只有8个字节的首部,比TCP的20个字节的首部要短。由于UDP没有拥塞控制,当网络出现拥塞不会使源主机的发送速率降低。因此UDP适用实时应用中要求源主机的有恒定发送速率的情况。

    TCP提供的服务

    1.端到端面向连接的服务;2. 完全可靠性;全双工通信;3.流接口;4.应用程序将数据流发送给TCP;5.在TCP流中,每个数据字节都被编号(序号);6.TCP层将数据流分成数据段并以序号来标识;7.可靠的连接建立和完美的连接终止。TCP 协议是面向字节的。TCP 将所要传送的报文看成

    运输层

    是字节组成的数据流,并使每一个字节对应于一个序号。 TCP的编号与确认:TCP不是按传送的报文段来编号。TCP将所要传送的整个报文看成是一个个字节组成的数据流,然后对每一个字节编一个序号。在连接建立时,双方要商定初始序号。TCP就将每一次所传送的报文段中的第一个数据字节的序号,放在TCP首部的序号字段中。TCP的确认是对接收到的数据的最高序号(即收到的数据流中的最后一个序号)表示确认。但返回的确认序号是已收到的数据的最高序号加1。也就是说,确认序号表示期望下次收到的第一个数据字节的序号。由于TCP能提供全双工通信,因此通信中的每一方都不必专门发送确认报文段,而可以在传送数据时顺便把确认信息捎带传送。这样可以提高传输效率。

    TCP报文段的格式

    从TCP报文段格式图可以看出,一个TCP报文段分为首部和数据两部分。

    首部固定部分各字段的意义如下:

    源端口/目的端口:TSAP地址。用于将若干高层协议向下复用

    发送序号:是本报文段所发送的数据部分第一个字节的序号

    确认序号:期望收到的数据(下一个消息)的第一字节的序号

    首部长度:单位为32位(双字) ;

    控制字段

    紧急比特(URG):URG=1时表示加急数据,此时紧急指针的值为加急数据的最后一个字节的序号 ;

    确认比特(ACK):ACK=1时表示确认序号字段有意义 ;

    急迫比特(PSH):PSH=1时表示请求接收端的传输实体尽快交付应用层

    复位比特(RST):RST=1表示出现严重差错,必须释放连接,重建 ;

    同步比特(SYN):SYN=1,ACK=0 表示连接请求消息。SYN=1,ACK=1 表示同意建立连接消息;

    终止比特(FIN):FIN=1时表示数据已发送完,要求释放连接;

    运输层

    窗口大小:通知发送方接收窗口的大小,即最多可以发送的字节数 ;

    检查和:12B的伪首部首部 数据;

    选项:长度可变。TCP只规定了一种选项,即最大报文段长度 ;

    TCP的流量控制

    TCP使用滑动窗口机制来进行流量控制。当一个连接建立时,连接的每一端分配一个缓冲区来保存输入的数据。当数据到达时,接收方发送确认ACK,并包含一个窗口通告(剩余的缓冲区空间的数量叫窗口)。如果发送方收到一个零窗口通告,将停止发送,直到收到一个正的窗口通告。当接收方窗口为0后应用层取出小部分数据将产生一个比较小的窗口通告,使得对方发送一些小的数据段,效益很低。可以通过延迟发送窗口通告或发送方延迟发送数据来解决。使用了窗口机制以后,提高了网络的吞吐量。

    TCP如何发现拥塞

    1.收到ICMP源抑制报文;2.超时包丢失;3.TCP把发现包丢失解释为网络拥塞

    拥塞避免:指当拥塞窗口增大到门限窗口时,

    运输层

    就将拥塞窗口指数增长速率降低为线性增长速率,避免网络再次出现拥塞。迅速递减:TCP总是假设大部分包丢失来源于拥塞,一旦包丢失,则TCP降低它发送数据的速率,这种方法能够缓和拥塞。慢启动:TCP开始时只发送一个消息;如果安全到达,TCP将发送两个消息;如果对应的两个确认来了,TCP就再发四个,如此指数增长一直持续到TCP发送的数据达到接收方通告窗口的一半,这时TCP将降低增长率。TCP的重传机制:TCP重传机制是TCP中最重要和最复杂的问题之一。TCP每发送一个报文段,就设置一次记时器。只要记时器设置的重传时间到而还没有收到确认,就要重传这一报文段。TCP监视每一连接中的当前延迟,并适配重发定时器来适应条件的变化。重发定时器基于连接往返延迟:

    RTTnew = (alpha*RTTold) ((1 - alpha)*RTTsample))

    RTO = beta*RTTnew

    TCP的运输连接管理

    1.运输连接管理目的:使运输连接的建立和释放都能正常的进行;2.连接建立的采用的过程叫做三次握手协议或三次联络。三次握手(three-way handshake)方案解决了由于网络层会丢失、存储和重复分组带来的问题。三次握手正常建立连接的过程:主机 A 发出序号为X的建立连接请求CR TPDU。主机 B 发出序号为Y的接受连接确认ACK TPDU,并确认A的序号为X的建立连接请求。主机 A 发出序号为X的第一个数据DATA,并确认主机 B的序号为Y的接受连接确认。

    TCP有限状态机:TCP将连接可能处于的状态及各种状态可能发生的变迁,画成如下图所示的有限状态机。图中的每一个方框就是TCP可能具有的状态。方框中写的字是TCP标准中给该状态起的名字。状态之间的箭头表示可能发生的状态变迁。箭头旁边写上的字,表示是什么原因引起这种变迁,或表明发生状态变迁后又出现什么动作。

    运输协议等级

    运输层的功能是要弥补从网络层获得的服务和拟向运输服务用户提供的服务之间的差距。它所关心的是提高服务质量包括优化成本。运输层的功能按级别和任选项划分,级别定义了一套功能集,任选项定义在一个级别内可以使用的功能。OSI定义了五种协议级别,即级别0(简单级)、级别1(基本差错恢复级)、级别2(多路复用级)、级别3(差错恢复和多中复用级)和级别4(差错检测和恢复级)。级别与任选项均可在连接建立过程中通过协商选用。运输层实体选用级别及任选项的依据为: 通过T-CNNECT语表示的运输服务用户的要求。 可用的网络服务质量。 传输服务用户所能与价格之比。

     

    根据用户要求和差错性质,网络服务按质量可划为下列三种类型:

    A型网络服务

    具有可接受的残留差错率和故障通知率(网络连接断开和复位发生的比率),也就是无N-RESET 完美的网络服务

    B型网络服务

    具有可接受的残留差错率和不可接受的故障通知率,即完美的分组递交但有N-RESET或N-ISCNNECT存在的网络服务

    C型网络服务

    具有不可接的残留差错,即网络连接不可靠,可能会丢失分组或出现重复分组,且存在N-RISCONNET的网络服务。 可见,网络服务质量的划分是以用记户要求比较高,则一个网络可能归于C型 ,反之则一网络可能于B型甚至A型 。例如:而同一网络对银行系统来说则只能算作C型了。三种类型的网络服务中,A型质量最高,分组的丢失,重复或复位等情况可以忽略不计,一般来说,能提供A型服务的公用宽或网几乎没有。B型网络质量次之,大多数X-RESET出现这就需要运输层协议来解决。c型网络服务质量最差,它是完全不可靠的服务,那些纯提供数据服务的宽域网,无线电分组交用网和很多国际网都属些类。服务质量划分得较高的网络,仅需要较简单的协议级别;反之,服务服务质量划分较低的网络,则需要复杂的协议。

    五种协议级别中,级别0提供简单的运输连接,它是专为A型网络设计的。级别0提供具有商的连接建立、分段和差错报告的数据运输所需要复杂的功能,以及网络服务提供的流量控制和拆线功能。级别1以最小开销提供了基本的运输连接,它是专为B型网络连接设计的。级别1提供具有运输连接,拆线和在一个网络连接上支持;连续的运输连接的能,并提供检级别0的功能以及在没有运输服务用户参予的情况下由网络层告警的故障恢复能力。级别2具有在一个络网连接,它是为与A型网络连接而设计的,级别2具有流量的控制的运输连接的能力。它不是供检错或差恢复功能。级别3提供级别2的功能以外,还提供具有在无运输服务用户参的情况下,检测由网络告警的故障恢复能力。级别4除提供级别3的功能以外,还提供具在

     

    无运输服务用户参情况下,检测由网络服务提供者提供低质量服务而引起的故障,并从故障中自行恢复的能力。所检测的故障类包括分组丢失、失序、重份和残缺。级别4还提供增强抗网络故障的能力。它是专为C网络连接设计的。【运输协议数据单元的定义和结构】运输协议数据单元(TPDU)结构是由数八位组(即字节)构成的,字节的编号从1开始,并按它们进入一个网络服务数据单元(NSDU)的顺序递增。每个字节中从1到8对比特进行编号最小的字节为最高有效值。TPDDU按顺序包含下列内容:头部、若存在,则占第n+1及其以后的字节;固定部分,占第2、3...、n个字节;可变部分,若存在,则占第n+1及其以后的字节。长度指示字段L1表示包含L1本身在内头部字节数长度,L1以二进制表示,最大值为254(11111110)。例如连接(CR)TPDU的长度不得超过128个字节。若指示的长度超过或实际的网络服务用户数据,则应视为协议出错。固定部分包括TPDU代码和常出现的参数。固定部分的部分的长度和结构由TPDU代码。

    TPDU代码表中,"XXXX"在级别2、3、4中标志许可证(CDT);在级别0和1中为"0000"。"ZZZZ" 在级别2、3、4中标志许可证;在选择接收确认任选规程时不能用。可变部分包含不常用的参数,各参数结构如图5.3所示,参数代码,字段用二进制编码参数长度指示以字节为单位指出参数值字段的长度。数据字段包含透明的用户数据,在每一个TPDU中,用户字段的长度均受限制,如cd/cc不能超过32个字节,DR不能超过64个字节,ED为1到16个字节,而DT用户数据长度受;连接建立时协商TPDU大小的限制

    运输服务

    运输层的最终目标是为用户提供有效、可靠和价格合理的服务。图5.1给出了运输层与网络层、运输服务用户三者之间的关系。在一个系统中,运输实体通过网络服务与其它运输实体通信,向运

    运输层

    输层用户(可以是应用进程,也可以是会话层协议)提供运输服务。运输层的服务包括的内容有:服务的类型、服务的等级、数据运输、用户接口、连接管理、快速数据运输、状态报告、安全保密等。

    服务类型

    运输服务有两大类,面向连接的服务和无连接的服务。面向连接的服务提供运输服务与用户之间逻辑连接的建立、维持和拆除,是可靠的服务,可提供流量控制、差错控制和序列控制。无连接服务即数据服务,只能提供不可靠的服务。

    服务等级

    运输协议实体应该允许运输层用户能选择运输层所提供的服务等级,以利于更有效地利用所提供的链路、网络及互连网络的资源。可供选择的服务包括差错和丢失数据的程度、允许的平均延迟和最大延迟、允许的平均吞吐率以及优先级水平等。根据这些要求,可将运输层协议服务等级细分为以下四类:

    1)可靠的面向连接的协议。2)不可靠的无连接协议。3)需要定序和定时运输的话音运输协议。4)需要快速和高可靠的实时协议。

    数据运输

    数据运输的任务是在两个运输实体之间运输用户数据和控制数据。一般采用全双工服务,个别场合也可采用半双工服务。数据可分为正常的服务数据分组和快速服务数据分组两种,对快速服务数据分组的运输可暂时中止当前的数据运输,在接收端用中断方式优先接收。

    用户接口

    用户接口机制可以有多种方式,包括采用过程调用、通过邮箱运输数据和参数、用DMA方式在主机

    运输层

    与具有运输层实体的前端处理机之间运输等。

    连接管理

    面向连接的协议需要提供建立和终止连接的功能。一般总是提供对称的功能,即两个对话的实体都有连接管理的功能,对简单的应用也有仅对一方提供连接管理功能的情况。连接的终止可以采用立即终止运输,或等待全部数据运输完再终止连接。

    状态报告

    向运输层用户提供运输实体或运输连接的状态信息。

    安全保密

    包括对发送者和接收者的确认、数据的加密以及通过和解密以及通过保密的链路和节点路由选择等安全保密的服务。

    服务质量

    服务质量QOS(Quality of Service)是指在运输连接点之间看到的某些运输连接的特征,是运输层能的度量,反映了运输质量及服务的可用性。服务质量可用一些参数来

    运输层

    描述,如连接建立延迟、连接建立失败、吞吐量、输送延迟、残留差错率、连接拆除延迟、连接拆除失败概率、连接回弹率、运输失败率等等。用户可以在连接建立时指明所期望的、可接受的或不可接受的QOS参数。通常,用户使用连接建立原语在用户与运输服务提供者之间协商QOS,协商过的QOS适用于整个运输连接的生存期。但主呼用户请求的QOS可能被运输服务提供者降低,也可能被呼用户降低。

    运输连接建立延迟是指在连接请求和相应的连接确认之间容许的最大延迟。运输连接失败概率是在一次测量样本中运输连接的失败总数与运输连接建立的全部尝试次数之比。连接失败定义为由于服务提供者方面的原因造成在规定的最大容许建立延迟时间内所请求的运输连接没有成功,而由于用户方面的原因造成的连接失败概率内。

    吞吐量是在某段时间间隙内单位时间运输的用户数据的字节数,对每个方向都有吞吐量,它们由最大吞吐量和平均吞吐量组成。输送延迟是在数据请求和相应的数据指示之间所经历的时间,每个方向都有输送延迟,包括最大输送延迟和平均输送延迟。残留差错率是在测量期间,所有错误的、丢失的和重复的用户数据与所请求的用户数据之比。运输失败概率是在进行样本测量期间观察到的运输失败总数与运输样本总数之比。

    运输连接拆除延迟是在用户发起除请求到成功地拆除运输连接之间可允许的最大延迟。运输连接拆除失败概率是引起拆除失败的拆除请求次数与在测量样本中拆除请求总次数之比。运输连接

    运输层

    保护是服务提供者为防止用户信息在未经许可的情况下被监视或操作的措施,保护选项的无保护特性、针对被动监视的保护及针对增、删、改的保护等。运输连接优先权为用户提供了指示不同的连接所具有的不同的重要性的方法。运输连接的回弹率是指在规定时间间隔(如1秒)内,服务提供者发起的连接拆除(即无连接拆除请求的连接拆除指示)的概率。QOS参数由运输服务用户在请求建立连接时加以说明,它可以给出所期望的值和可接受的值,在某些情况下,运输实体在检查QOS参数时能立即发现其中一些值是无法实现的,在这种情况下,运输实体直接将连接失败的信息告诉的信息告诉请求者,同时说明失败的原因。另外的一种情况是运输层知道它无法实现用户期望的要求(例如1200bps的吞量),但能达到稍低一点的且仍能被用户所接受的值(例如600bps的吞吐量),那么它就在请求建立连接时向目的机发出这一值。如果目的机不能处理高于源端机可接受的值(例如300bps的吞吐量),那么它就可以将参数降至该可接受值,若目的机连该可接受值也不能处理,则拒绝连接请求。由此,请求者总能立即知道建立是否成功,若成功

    运输层

    则商定的QOS是什么等信息。以上过程称为选项协商,一旦各种参数协商好,则在整个连接生存期内保持不变。上述QOS参数中的一部分也选用于无连接运输服务。

    OSI运输服务定义(ISO8072)没有具体给出QOS参数的编码或允许值,这些参数通常在用户与电信部门之间商定。为防止某些用户对QOS过于贪心,大多数电信部门对于较高质量的服务相应地也收取较高的费用。OSI运输服务原语传输层为上一层的应用程序提供一个标准的原语集,为服务提供者和用户之间进行可靠的数据传输架起了一座“桥梁”。ISO规范包括四种类型10个运输服务原语,见表5.2。其中服务质量参数指示用户的要求,诸如吞吐量、延迟、可靠度和优先度等。运输 服务(TS)用户数据参数最多可达32个八进制用户数据。


    展开全文
  • Linux系统对IO端口和IO内存的管理 一、I/O端口 二、IO内存 三、IO端口和IO内存的区分及联系 四、外设IO端口物理地址的编址方式 统一编址 独立编址 优缺点 五、...
  • IO端口&IO内存

    2016-09-01 16:49:25
    外设寄存器也称为I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式,可以把CPU分成两大类。一类CPU(如M68K,Power PC等)把这些寄存器看作内存的一部分,寄存器参与...
  • IO端口&IO内存

    2018-03-14 11:39:43
    IO端口和IO内存的区别及分别使用的函数接口分类: 苦与乐---linux 2012-09-23 23:29 6603人阅读 评论(8)收藏 举报iolinux内核存储x86数据结构linux目录(?)[+]IO端口和IO内存的区别及分别使用的函数接口 每个外设都...
  • 外设寄存器也称为I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式,可以把CPU分成两大类。一类CPU(如M68K,Power PC等)把这些寄存器看作内存的一部分,寄存器参与...
  • 在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据,见下图,某些通讯中还需要双方约定数据的传输速率,以便更好地同步。...
  • ② 被连接网络完全相同 : 中继器 可以 将 完全相同的 网络 互连 , 两个网段 速率 必须相同 ; ③ 电气部分作用 : 中继器 将 一条电缆段 的数据 , 发送到 另一条电缆段 , 其 仅作用于 信号的 电气部分 , 不会校验数据...
  • 它可以支持最高 10Mbps 下载速率和 5 Mbps 上传速率。 A7670C具有强大的扩展能力,包括UART,I2C,GPIO等丰富的接口。该模块为客户的应用提供了极大的灵活性和易集成性, A7670C的封装形式是LGA,与SIM7070系列模块...
  • 2、4字节文件长度刚好和整型相等,在两个32位小端机器上直接拷贝发送,代码简单。 3、文件长度字段可以方便检查数据是否接收完全,解决粘包问题。 4、局域网内网络相对比较好,所以没带文件校验。 测试结果 方案1,...
  • 从编程的角度来说,Windows的代码风格是使用所谓的匈牙利命名法,而linux使用的短小精悍的连字符风格,例如同一个表示屏幕尺寸的整型变量,Windows 上可能被命名为 iScreen 或 cxScreen ,而 linux 可能是 screen;...
  • hw parameter表示当前音频流使用的硬件参数,比如当前音频流使用的format,声道数,使用的哪个音频输出端口等等。 因为一个codec或者一个dai可以支持多种格式,比如一个dai可以传16bit pcm、24bit pcm,那么现在究竟...
  • IO端口和IO内存的区别及分别使用的函数接口   每个外设都是通过读写其寄存器来控制的。外设寄存器也称为I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式,可以把...
  • 在Java程序中,为了实现同本地系统的硬件端口(RS-232标准串口/IEEE1284标准并口)通讯,目前有两个方法,第一个就是以前说过的,使用JNI技术自己写一个与java程序相关的dll/so文件,这个文件可以直接和本地系统通信,从而...
  • UDP 数据包会沿协议栈向上一直到 UDP 层,因为到 UDP 层,端口不匹配的话,数据才会丢弃,如下图,所以,运行音视频等较高速率工作的应用,会带来较大的负担。 04. setsockopt函数 默认的情况下,不允许发送广播...
  • 荣耀2022数字芯片设计提前批 时间2021.8.03一个小时 ...所有寄存器的异步置位及异步复位端口上未叠加任何用户功能逻辑 C.同步设计便于静态时序分析 D.所有寄存器均采用同一时钟 解:D 分频也算啊 3.[单选题...
  • 华为S9300交换机QOS配置

    千次阅读 2020-12-30 11:19:33
    S9300的端口的队列调度机制每个物理端口有8个发送队列,队列7优先级最高,依次类推队列0优先级最低。发送端口支持队列调度方式有PQ、WRR和DRR,并且支持PQ+WRR和PQ+DRR的混合调度。www.iyunv.com混合调度时,首先...
  • 传输层标识网络进程的数字称为传输层地址或端口号。通过该方法,传输层可以使多对进程间的通信复用到一个网络连接上,以此来完成多对应用程序间的通信 2.TCP所使用的计时器中,哪个计时器的超时值会随着通信的进行...
  • IO端口和IO内存的区别及分别使用的函数接口 每个外设都是通过读写其寄存器来控制的。外设寄存器也称为I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式,可以把CPU分成...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,390
精华内容 956
关键字:

端口整型速率