Linux设备驱动开发详解笔记二

第11章 内存与I/O访问

每个进程的用户空间都是完全独立、 互不相干的, 用户进程各自有不同的页表。 而内核空间是由内核
负责映射, 它并不会跟着进程改变, 是固定的。 内核空间的虚拟地址到物理地址映射是被所有进程共享
的, 内核的虚拟空间独立于其他程序
11.3 内存存取
11.3.1 用户空间内存动态申请
在用户空间中动态申请内存的函数为malloc() , 这个函数在各种操作系统上的使用都是一致的,
malloc() 申请的内存的释放函数为free() 。 对于Linux而言, C库的malloc() 函数一般通过brk() 和
mmap() 两个系统调用从内核申请内存。
由于用户空间C库的malloc算法实际上具备一个二次管理能力, 所以并不是每次申请和释放内存都一
定伴随着对内核的系统调用。 比如, 代码清单11.2的应用程序可以从内核拿到内存后, 立即调用
free() , 由于free() 之前调用了mallopt(M_TRIM_THRESHOLD, -1) 和
mallopt(M_MMAP_MAX, 0) , 这个free() 并不会把内存还给内核, 而只是还给了C库的分配算法(内
存仍然属于这个进程) , 因此之后所有的动态内存申请和释放都在用户态下进行。
代码清单11.2 用户空间内存申请以及mallopt

#include <malloc.h>
#include <sys/mman.h>
#define SOMESIZE (100*1024*1024) // 100MB

int main(int argc, char *argv[])
{
 unsigned char *buffer;
 int i;

 if (mlockall(MCL_CURRENT | MCL_FUTURE))
 mallopt(M_TRIM_THRESHOLD, -1);
 mallopt(M_MMAP_MAX, 0);

 buffer = malloc(SOMESIZE);
 if (!buffer)
 exit(-1);

 /*
 * Touch each page in this piece of memory to get it
 * mapped into RAM
 */
 for (i = 0; i < SOMESIZE; i += page_size)
 buffer[i] = 0;
 free(buffer);
 /* <do your RT-thing> */

 return 0;
}


另外, Linux内核总是采用按需调页(Demand Paging) , 因此当malloc() 返回的时候, 虽然是成功
返回, 但是内核并没有真正给这个进程内存, 这个时候如果去读申请的内存, 内容全部是0, 这个页面的
映射是只读的。 只有当写到某个页面的时候, 内核才在页错误后, 真正把这个页面给这个进程。

11.4 设备I/O端口和I/O内存的访问
设备通常会提供一组寄存器来控制设备、 读写设备和获取设备状态, 即控制寄存器、 数据寄存器和状
态寄存器。 这些寄存器可能位于I/O空间中, 也可能位于内存空间中。 当位于I/O空间时, 通常被称为I/O端
口; 当位于内存空间时, 对应的内存空间被称为I/O内存。

11.4.1 Linux I/O端口和I/O内存访问接口
1.I/O端口
在Linux设备驱动中, 应使用Linux内核提供的函数来访问定位于I/O空间的端口, 这些函数包括如下几
种。
1) 读写字节端口(8位宽) 。
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
2) 读写字端口(16位宽) 。
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
3) 读写长字端口(32位宽) 。
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
4) 读写一串字节。
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
5) insb() 从端口port开始读count个字节端口, 并将读取结果写入addr指向的内存; outsb() 将addr
指向的内存中的count个字节连续写入以port开始的端口。
6) 读写一串字。
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
7) 读写一串长字。
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函数中I/O端口号port的类型高度依赖于具体的硬件平台, 因此, 这里只是写出了unsigned。
2.I/O内存
在内核中访问I/O内存(通常是芯片内部的各个I2C、 SPI、 USB等控制器的寄存器或者外部内存总线上的设备) 之前, 需首先使用ioremap() 函数将设备所处的物理地址映射到虚拟地址上。 ioremap() 的
原型如下:
void *ioremap(unsigned long offset, unsigned long size);
ioremap() 与vmalloc() 类似, 也需要建立新的页表, 但是它并不进行vmalloc() 中所执行的内存
分配行为。 ioremap() 返回一个特殊的虚拟地址, 该地址可用来存取特定的物理地址范围, 这个虚拟地
址位于vmalloc映射区域。 通过ioremap() 获得的虚拟地址应该被iounmap() 函数释放, 其原型如下:
void iounmap(void * addr);
 

11.4.4 将设备地址映射到用户空间
1.内存映射与VMA
用户空间是不可能也不应该直接访问设备的, 但是, 设备驱动程序中可实现mmap()
函数, 这个函数可使得用户空间能直接访问设备的物理地址。 将用户空间的一段内存与设备内存关联, 当用户访问用户空间的这段地址范围时, 
实际上会转化为对设备的访问。
这种能力对于显示适配器一类的设备非常有意义, 如果用户空间可直接通过内存映射访问显存的话,
屏幕帧的各点像素将不再需要一个从用户空间到内核空间的复制的过程。
mmap() 必须以PAGE_SIZE为单位进行映射, 实际上, 内存只能以页为单位进行映射, 若要映射非
PAGE_SIZE整数倍的地址范围, 要先进行页对齐, 强行以PAGE_SIZE的倍数大小进行映射。
从file_operations文件操作结构体可以看出, 驱动中mmap() 函数的原型如下:
int(*mmap)(struct file *, struct vm_area_struct*);
驱动中的mmap() 函数将在用户进行mmap() 系统调用时最终被调用, mmap() 系统调用的原型
与file_operations中mmap() 的原型区别很大, 如下所示:
caddr_t mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);

当用户调用mmap() 的时候, 内核会进行如下处理。
1) 在进程的虚拟空间查找一块VMA。2) 将这块VMA进行映射。
3) 如果设备驱动程序或者文件系统的file_operations定义了mmap( ) 操作, 则调用它。
4) 将这个VMA插入进程的VMA链表中。
file_operations中mmap( ) 函数的第一个参数就是步骤1) 找到的VMA。
由mmap( ) 系统调用映射的内存可由munmap( ) 解除映射, 这个函数的原型如下:
int munmap(caddr_t addr, size_t len );

大多数设备驱动都不需要提供设备内存到用户空间的映射能力, 因为, 对于串口等面向流的设
备而言, 实现这种映射毫无意义。 而对于显示、 视频等设备, 建立映射可减少用户空间和内核空间之间的
内存复制

11.5 I/O内存静态映射

建立外设I/O内存物理地址到虚拟地址的静态映射  map_desc
map_desc结构体
struct map_desc {
 unsigned long virtual; /* 虚拟地址 */
 unsigned long pfn ; /* __phys_to_pfn(phy_addr) */
 unsigned long length; /* 大小 */
 unsigned int type; /* 类型 */
};

static struct map_desc ixdp2x01_io_desc _ _initdata = {
 .virtual = IXDP2X01_VIRT_CPLD_BASE,
 .pfn = _ _phys_to_pfn(IXDP2X01_PHYS_CPLD_BASE),
 .length = IXDP2X01_CPLD_REGION_SIZE,
 .type = MT_DEVICE
};

static void _ _init ixdp2x01_map_io(void)
{
 ixp2000_map_io();
 iotable_init(&ixdp2x01_io_desc, 1);//建立页映射

}

11.6 DMA

DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。 使用DMA
可以使系统CPU从实际的I/O数据传输过程中摆脱出来, 从而大大提高系统的吞吐率。

DMA方式的数据传输由DMA控制器(DMAC) 控制, 在传输期间, CPU可以并发地执行其他任务。
当DMA结束后, DMAC通过中断通知CPU数据传输已经结束, 然后由CPU执行相应的中断服务程序进行
后处理。
 

第12章 Linux设备驱动的软件架构思想
 

猜你喜欢

转载自blog.csdn.net/csdn1126274345/article/details/102539664