Linux proc iomem与 proc ioports

* Ok, go for it…
*/
//将 area区域家兔vmalloc区中的vmlist
area = get_vm_area_caller(size, VM_IOREMAP, caller);

}


可以看到在ioremap映射的过程中,会检查要映射的物理地址是否是System RAM,如果是则不会映射。



struct vm_struct *get_vm_area_caller(unsigned long size, unsigned long flags,
const void *caller)
{
return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END,
NUMA_NO_NODE, GFP_KERNEL, caller);
}


get\_vm\_area\_caller调用\_\_get\_vm\_area\_node函数,接下来流程就与vmalloc的实现基本一致了,请参考:[Linux vmalloc原理与实现](https://bbs.csdn.net/topics/618542503),找到一个vmalloc子区域,建立vmalloc子区域与物理地址的四级页表映射:PGD -> PUD -> PMD -> PTE。


几乎每一种外设都是通过读写设备上的相关寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。


ioremap的返回值可以传递给一组访问器函数(名称如 readb() 或 writel()),以实际将数据移入或移出 I/O 内存。 在某些体系结构(尤其是 x86)上,I/O 内存真正映射到内核的内存空间(内核虚拟地址空间),因此这些访问器函数变成了直接的指针解引用。  
 使用ioremap映射完成后,内核空间可以使用ioremap返回的地址对I/O memory进行读写。  
 从 ioremap 返回的地址不应当直接解引用; 相反, 应当使用内核提供的存取函数。ioremap\_nocache 执行平台特定的操作序列,以使总线内存 CPU 可通过 readb/readw/readl/writeb/writew/writel 函数访问。返回的地址不保证可直接用作虚拟地址。


简单点说就是不应该直接使用通过ioremap返回的内核虚拟地址,而是通过辅助函数 readb/readw/readl/writeb/writew/writel 等来进行访问ioremap返回的内核虚拟地址。  
 尽管在 x86 上解引用一个指针能工作, 不使用正确的辅助函数(宏定义)不利于驱动的移植性和可读性。


依赖计算机平台和使用的总线, I/O 内存可以或者不可以通过页表来存取. 当通过页表存取, 内核必须首先安排从你的驱动可见的物理地址, 并且这常常意味着你在做任何 I/O 之前必须调用 ioremap 。


备注: \_\_iomem 注释,用于标记指向 I/O 内存的指针。 这些注释的工作方式与 \_\_user 标记非常相似,只是它们引用了不同的地址空间。 与 \_\_user 一样,\_\_iomem 标记在内核代码中充当文档角色; 它被编译器忽略。


ioremap不能映射System RAM。


### 1.3 mmap


简单介绍下mmap:  
 mmap映射一个设备意味着关联一些用户空间地址到设备内存. 无论何时程序在给定范围内读或写, 它实际上是在存取设备。 为实现 mmap, 驱动只要建立合适的页表给这个地址范围。通过调用 remap\_pfn\_range来建立页表。



struct file_operations {
int (*mmap) (struct file *, struct vm_area_struct *);
};


vma 包含关于用来存取设备的虚拟地址范围的信息,指定了用户地址空间内连续区间的一个独立内存范围。



// /include/linux/mm_types.h

/*
* This struct defines a memory VMM memory area. There is one of these
* per VM-area/task. A VM area is any part of the process virtual memory
* space that has a special rule for the page-fault handlers (ie a shared
* library, the executable area etc).
*/
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */

unsigned long vm_start;		/\* Our start address within vm\_mm. \*/
unsigned long vm_end;		/\* The first byte after our end address

within vm_mm. */

/\* linked list of VM areas per task, sorted by address \*/
struct vm\_area\_struct \*vm_next, \*vm_prev;

struct rb\_node vm_rb;

/\*

* Largest free memory gap in bytes to the left of this VMA.
* Either between this VMA and vma->vm_prev, or between one of the
* VMAs below us in the VMA rbtree and its ->vm_prev. This helps
* get_unmapped_area find a free area of the right size.
*/
unsigned long rb_subtree_gap;

/\* Second cache line starts here. \*/

struct mm\_struct \*vm_mm;	/\* The address space we belong to. \*/
pgprot\_t vm_page_prot;		/\* Access permissions of this VMA. \*/
unsigned long vm_flags;		/\* Flags, see mm.h. \*/

};


remap\_pfn\_range,建立页表,完成物理地址到用户空间虚拟地址的映射。remap\_pfn\_range将物理页帧号pfn\_start对应的物理内存映射到用户空间的vm->vm\_start处,映射长度为该虚拟内存区的长。



// mm/memory.c

/**
* remap_pfn_range - remap kernel memory to userspace
* @vma: user vma to map to
* @addr: target user address to start at
* @pfn: physical address of kernel memory
* @size: size of map area
* @prot: page protection flags for this mapping
*
* Note: this is only safe if the mm semaphore is held when called.
*/
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
{
pgd_t *pgd;
unsigned long next;
unsigned long end = addr + PAGE_ALIGN(size);
struct mm_struct *mm = vma->vm_mm;
int err;

/\*

* Physically remapped pages are special. Tell the
* rest of the world about it:
* VM_IO tells people not to look at these pages
* (accesses can have side effects).
* VM_PFNMAP tells the core MM that the base pages are just
* raw PFN mappings, and do not have a “struct page” associated
* with them.
* VM_DONTEXPAND
* Disable vma merging and expanding with mremap().
* VM_DONTDUMP
* Omit vma from core dump, even when VM_IO turned off.
*
* There’s a horrible special case to handle copy-on-write
* behaviour that some programs depend on. We mark the “original”
* un-COW’ed pages by matching them up with “vma->vm_pgoff”.
* See vm_normal_page() for details.
*/
if (is_cow_mapping(vma->vm_flags)) {
if (addr != vma->vm_start || end != vma->vm_end)
return -EINVAL;
vma->vm_pgoff = pfn;
}

err = track\_pfn\_remap(vma, &prot, pfn, addr, PAGE\_ALIGN(size));
if (err)
	return -EINVAL;

vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;

BUG\_ON(addr >= end);
pfn -= addr >> PAGE_SHIFT;
pgd = pgd\_offset(mm, addr);
flush\_cache\_range(vma, addr, end);
do {
	next = pgd\_addr\_end(addr, end);
	err = remap\_pud\_range(mm, pgd, addr, next,
			pfn + (addr >> PAGE_SHIFT), prot);
	if (err)
		break;
} while (pgd++, addr = next, addr != end);

if (err)
	untrack\_pfn(vma, pfn, PAGE\_ALIGN(size));

return err;

}
EXPORT_SYMBOL(remap_pfn_range);


一个简单在驱动中实现的例子:



my_mmap_func(){

remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot);

}

struct file_operations my_dev{
.mmap = my_mmap_func(struct file *, struct vm_area_struct *);
};


用户打开定义的设备,然后调用mmap时,驱动中的file\_operations->mmap: my\_mmap\_func会被调用, my\_mmap\_func中调用remap\_pfn\_range完成物理地址到用户虚拟地址空间的映射。  
 mmap映射一个设备意味着关联一些用户空间地址到设备内存. 无论何时程序在给定范围内读或写, 它实际上是在存取设备。


## 二、struct resource


Linux设计了一个通用的数据结构resource来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。  
 Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是个resource结构,而树的根结点root则描述了该类资源的整个资源空间。  
 基于上述这个思想,Linux将基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)。



// /include/linux/ioport.h
/*
* Resources are tree-like, allowing
* nesting etc…
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};

/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000

#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000

#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */

#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */

#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */


对于flags主要是:



#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */

#define IORESOURCE_IO 0x00000100 /* ioports */
#define IORESOURCE_MEM 0x00000200 /* iomem */


## 三、System RAM


### 3.1 System RAM 简介


在 /proc/iomem 的输出中,所有 RAM 范围都被命名为“系统 RAM”。  
 System RAM:DDR物理内存,内存条。  
 System RAM 不一定位于物理地址空间的开头,也不总是在一个连续的块中。 为了确定物理地址空间的哪些部分是System RAM(相对于内存映射 I/O),我们通过 /proc/iomem来查找。



cat /proc/iomem | grep “System RAM”


![在这里插入图片描述](https://img-blog.csdnimg.cn/1faa052577c54a66af3b325dbf759080.png)


### 3.2 page\_is\_ram


该函数功能:给定的页框号是否属于物理内存,主要是在iomem\_resource 这颗资源树上查找名为"System Ram" 的资源,如果包含在其中的话,就说明该页框号属于物理内存。  
 如果指定地址在 iomem\_resource 列表中注册为“系统 RAM”,则此通用 page\_is\_ram() 返回 true。



// /kernel/resource.c

/*
* This function calls callback against all memory range of “System RAM”
* which are marked as IORESOURCE_MEM and IORESOUCE_BUSY.
* Now, this function is only for “System RAM”.
*/
int walk_system_ram_range(unsigned long start_pfn, unsigned long nr_pages,
void *arg, int (*func)(unsigned long, unsigned long, void *))
{
struct resource res;
unsigned long pfn, end_pfn;
u64 orig_end;
int ret = -1;

res.start = (u64) start_pfn << PAGE_SHIFT;
res.end = ((u64)(start_pfn + nr_pages) << PAGE_SHIFT) - 1;
res.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
orig_end = res.end;
while ((res.start < res.end) &&
	(find\_next\_system\_ram(&res, "System RAM") >= 0)) {
	pfn = (res.start + PAGE_SIZE - 1) >> PAGE_SHIFT;
	end_pfn = (res.end + 1) >> PAGE_SHIFT;
	if (end_pfn > pfn)
		ret = (\*func)(pfn, end_pfn - pfn, arg);
	if (ret)
		break;
	res.start = res.end + 1;
	res.end = orig_end;
}
return ret;

}

/*
* This generic page_is_ram() returns true if specified address is
* registered as “System RAM” in iomem_resource list.
*/
int __weak page_is_ram(unsigned long pfn)
{
return walk_system_ram_range(pfn, 1, NULL, __is_ram) == 1;
}


### 3.3 Kernel code、data、bss


系统会使用System RAM其中的一部分自用,放置code、data、bss和crash kernel。这部分物理内存已经被使用,系统不会使用这部分物理内存用来别的用途。



cat /proc/iomem | grep -i kernel


![在这里插入图片描述](https://img-blog.csdnimg.cn/4691d97ace4343a0b7f95007a65a4d42.png)



// /arch/x86/kernel/setup.c
/*
* Machine setup…
*/
static struct resource data_resource = {
.name = “Kernel data”,
.start = 0,
.end = 0,
.flags = IORESOURCE_BUSY | IORESOURCE_MEM
};

static struct resource code_resource = {
.name = “Kernel code”,
.start = 0,
.end = 0,
.flags = IORESOURCE_BUSY | IORESOURCE_MEM
};

static struct resource bss_resource = {
.name = “Kernel bss”,
.start = 0,
.end = 0,
.flags = IORESOURCE_BUSY | IORESOURCE_MEM
};



// /include/linux/sched.h

extern struct mm_struct init_mm;



// /arch/x86/kernel/setup.c
void __init setup_arch(char **cmdline_p){

init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = _brk_end;

code_resource.start = \_\_pa\_symbol(_text);
code_resource.end = \_\_pa\_symbol(_etext)-1;
data_resource.start = \_\_pa\_symbol(_etext);
data_resource.end = \_\_pa\_symbol(_edata)-1;
bss_resource.start = \_\_pa\_symbol(__bss_start);
bss_resource.end = \_\_pa\_symbol(__bss_stop)-1;

......

/\* after parse\_early\_param, so could debug it \*/
insert\_resource(&iomem_resource, &code_resource);
insert\_resource(&iomem_resource, &data_resource);
insert\_resource(&iomem_resource, &bss_resource);

......

}


insert\_resource函数将设备的物理地址资源注册到资源树中。


## 四、/proc/ioports


/proc/ioports 的输出提供了当前注册的端口区域列表,用于与设备进行输入或输出通信。如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/bbc89332df984343818479d1b4bff7d3.png)  
 第一列给出了为第二列中列出的设备保留的 I/O 端口地址范围,I/O端口地址空间通常都比较小。CPU通过设立专门的IN和OUT指令来访问这一空间中的地址单元(即I/O端口)。这里不做过多描述。



// /arch/x86/kernel/setup.c
static struct resource standard_io_resources[] = {
{ .name = “dma1”, .start = 0x00, .end = 0x1f,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “pic1”, .start = 0x20, .end = 0x21,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “timer0”, .start = 0x40, .end = 0x43,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “timer1”, .start = 0x50, .end = 0x53,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “keyboard”, .start = 0x60, .end = 0x60,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “keyboard”, .start = 0x64, .end = 0x64,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “dma page reg”, .start = 0x80, .end = 0x8f,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “pic2”, .start = 0xa0, .end = 0xa1,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “dma2”, .start = 0xc0, .end = 0xdf,
.flags = IORESOURCE_BUSY | IORESOURCE_IO },
{ .name = “fpu”, .start = 0xf0, .end = 0xff,
.flags = IORESOURCE_BUSY | IORESOURCE_IO }
};


## 五、/proc/iomem/与/proc/ioports/对比


即便外设总线有一个单独的地址空间给 I/O 端口, 不是所有的设备映射它们的寄存器到 I/O 端口. 虽然对于 ISA 外设板使用 I/O 端口是普遍的, 大部分 PCI 设备映射寄存器到一个内存地址区. 这种 I/O 内存方法通常是首选的, 因为它不需要使用特殊目的处理器指令; CPU 核存取内存更加有效, 并且编译器当存取内存时有更多自由在寄存器分配和寻址模式的选择上。  
 备注:尽量使用I/O 内存方法。


对外设的操作实际上是通过读写外设中的memory或者register来完成的。操作方式有两种:I/O ports操作方式和I/O memory操作方式。  
 每个外设都是通过读写它的寄存器来控制. 大部分时间一个设备有几个寄存器, 并且在连续地址存取它们, 或者在内存地址空间或者在 I/O 地址空间. 在硬件级别上, 内存区和 I/O 区域没有概念上的区别: 它们都是通过在地址总线和控制总线上发出电信号来存取(即, 读写信号)并且读自或者写到数据总线。


### 5.1 API简介


(1)request\_mem\_region  
 使用I/O memory首先要申请,申请I/O memory的函数是request\_mem\_region,I/O 内存区必须在使用前分配,然后才能映射。request\_mem\_region函数并没有做映射工作,主要是申请一块busy resource region。完成映射工作的主要还是ioremap函数,ioremap主要是检查传入地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换。



#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)


(2)request\_region  
 使用I/O端口首先要申请, 申请I/O端口的函数是request\_region,对I/O端口的请求是让内核知道你要访问该端口,内核并让你独占该端口.



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


对I/O端口的操作,CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元(也即I/O端口):



// arch/x86/include/asm/io.h

#define BUILDIO(bwl, bw, type)
static inline void out##bwl(unsigned type value, int port)
{
asm volatile(“out” #bwl " %" #bw “0, %w1”
: : “a”(value), “Nd”(port));
}

static inline unsigned type in##bwl(int port)
{
unsigned type value;
asm volatile(“in” #bwl " %w1, %" #bw “0”
: “=a”(value) : “Nd”(port));
return value;
}

static inline void out##bwl##_p(unsigned type value, int port)
{
out##bwl(value, port);
slow_down_io();
}

static inline unsigned type in##bwl##_p(int port)
{
unsigned type value = in##bwl(port);
slow_down_io();
return value;
}

static inline void outs##bwl(int port, const void *addr, unsigned long count)
{
asm volatile(“rep; outs” #bwl
: “+S”(addr), “+c”(count) : “d”(port));
}

static inline void ins##bwl(int port, void *addr, unsigned long count)
{
asm volatile(“rep; ins” #bwl
: “+D”(addr), “+c”(count) : “d”(port));
}

BUILDIO(b, b, char)
BUILDIO(w, w, short)
BUILDIO(l, , int)


(3)\_\_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);

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

给大家整理的电子书资料:

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
flags)
{
DECLARE_WAITQUEUE(wait, current);
struct resource *res = alloc_resource(GFP_KERNEL);

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

[外链图片转存中…(img-qEvKtUJN-1714130875716)]

给大家整理的电子书资料:

[外链图片转存中…(img-ANjtLd1v-1714130875717)]

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值