mmap underlying driver implementation (remap_pfn_range function)

Mmap underlying driver implementation

myfb.c (applied for 128K space)
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/export.h>
#include <linux/mm_types.h>
#include <linux/mm.h>
#include <linux/slab.h>

#define BUFF_SIZE (32 * 4 * 1024)

static char *buff;
static int major;
static struct class * myfb_class;

static int myfb_mmap (struct file *fp, struct vm_area_struct *vm)
{<!-- -->
int res;
//Indicates the offset address of the vma in the virtual address space, the unit is page (4K)
//unsigned long offset = vm->vm_pgoff << PAGE_SHIFT;
\t
//Calculate the physical page frame number (physical address + offset) of the mapped physical memory, in page units, virt_to_phys converts the virtual address into a physical address
//vm->pgoff represents the offset in the VMA during user space mapping (that is, the last parameter of mmap, the unit is bytes, but vm->pgoff is automatically converted into page units)
unsigned long pfn_start = (virt_to_phys(buff) >> PAGE_SHIFT);

res = remap_pfn_range(vm, vm->vm_start,
pfn_start + vm->vm_pgoff, //Add offset to the physical page frame number
vm->vm_end - vm->vm_start,
vm->vm_page_prot);
if(res){<!-- -->
printk("remap_pfn_range failed\
");
return -1;
}

printk("[kernel] pfn_start = 0x%lx, vm->vm_pgoff = 0x%lx, \
\
[kernel] vm->vm_start = 0x%lx, vm->vm_end = 0x%lx, vir_ker_start = 0x%lx\
", \
pfn_start, vm->vm_pgoff, vm->vm_start, vm->vm_end, (unsigned long)buff);
return 0;
}

static struct file_operations myfb_fops = {<!-- -->
.owner = THIS_MODULE,
.mmap = myfb_mmap,
};

static int myfb_init(void)
{<!-- -->
buff = kzalloc(BUFF_SIZE, GFP_KERNEL);
if (!buff){<!-- -->
printk("kzalloc failed!\
");
return -ENOMEM;
}
printk("kzalloc success!\
");

major = register_chrdev(0, "myfb", & amp;myfb_fops);
myfb_class = class_create(THIS_MODULE, "myfb_class");
device_create(myfb_class, NULL, MKDEV(major, 0), NULL, "myfb");
\t
return 0;
}

static void myfb_exit(void)
{<!-- -->
device_destroy(myfb_class, MKDEV(major, 0));
class_destroy(myfb_class);
unregister_chrdev(major, "myfb");
\t
kfree(buff);
}


module_init(myfb_init);
module_exit(myfb_exit);
MODULE_LICENSE("GPL");



mmap_read.c
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define PAGE_SIZE (4*1024)
#define BUFF_SIZE (1 * PAGE_SIZE)
#define OFFSET (2 * PAGE_SIZE)

char *p;
int fd;

void ctrlc(int signum)
{<!-- -->
munmap(p, BUFF_SIZE);
close(fd);
}

int main(void)
{<!-- -->
signal(SIGINT, ctrlc);
fd = open("/dev/myfb", O_RDWR);

       p = (char *)mmap(NULL, BUFF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, OFFSET);

if(p){<!-- -->
printf("mmap addr = 0x%x\
", p);
printf("data = %s\
", p);
}else{<!-- -->
printf("mmap failed\
");
}

while(1){<!-- -->
sleep(1);
}

return 0;
}
mmap_write.c
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define PAGE_SIZE (4*1024)
#define BUFF_SIZE (1 * PAGE_SIZE)
#define OFFSET (0 * PAGE_SIZE)

char *p;
int fd;

void ctrlc(int signum)
{<!-- -->
munmap(p, BUFF_SIZE);
close(fd);
}

int main(void)
{<!-- -->
signal(SIGINT, ctrlc);

fd = open("/dev/myfb", O_RDWR);

       p = (char *)mmap(NULL, BUFF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, OFFSET);

if(p){<!-- -->
printf("mmap addr = 0x%x\
", p);
memcpy(p, "hello world", 20);
}else{<!-- -->
printf("mmap failed\
");
}

while(1){<!-- -->
sleep(1);
}
return 0;
}

Makefile

KERNEL_DIR = /home/me/Kernel_Uboot/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

all:
make -C $(KERNEL_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc mmap_read.c -o mmap_read
$(CROSS_COMPILE)gcc mmap_write.c -o mmap_write

clean:
make -C $(KERNEL_DIR) M=`pwd` modules clean

obj-m + = myfb.o

When the offset of the memory mapping address of the reading and writing processes is both 0, the reading process can read out the data written by the writing process.

When the memory mapping address of the writing process has an offset of 0 and the memory mapping address of the reading process has an offset of 2 (unit page), the data read by the reading process is empty.

PS: Note that the pfn_start of the reading and writing processes is the same. This value is the mapped physical memory address, vm->vm_pgoff is the offset (unit page, one page = 4K (4096))

However, the results of the virtual addresses mapped by the two processes are not necessarily the same. The virtual address is unique to the process itself, which is easy to understand.

1. View virtual memory distribution

Check the pid of the read and write process

Virtual memory distribution of writing processes

Virtual memory distribution of reading process

1.1 Analysis of virtual memory mapping part

Taking the reading process as an example, the line where /dev/myfb is located is the memory mapping part.

76ffa000: The value of vm->vm_start

76ffb000: The value of vm->vm_end

rw-s: represents vm->vm_flags, “rw” represents readable and writable, “s” represents share sharing, and “p” represents private.

00000000: represents the offset, that is, vm->vm_pgoff (unit page, the offset unit here is bytes, you need to do some conversion)

00:06: represents the primary and secondary device numbers

2564: represents the inode value

/dev/myfb: represents the device node name

1.2 About offsets

After changing the last parameter of the mmap function in the reading process to 2*4096 (2 pages), the address distribution of the virtual memory mapping part of the process is as follows.

You can see that the offset is 00002000, that is, 2*4096 bytes = 2 pages = 8K

The offset refers to the last parameter of mmap, which is also vm->vm_pgoff (unit page). It refers to the offset on the physical memory of the file during mapping. Only part of the content of the file is mapped. The unit is Page 4K

2. remap_pfn_range function
2.1 remap_pfn_range function prototype
/**
 * 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)
Parameter Meaning
vm Virtual memory area descriptor, used to represent the mapped virtual memory area
addr The first address of the mapped virtual memory area
pfn Physical page frame number
size The size of the mapping area
prot Operation attributes of the physical page, such as read/write/execute permissions
2.2 Use of remap_pfn_range function

Here we mainly understand the following code in remap_pfn_range in the myfb.c driver code

unsigned long pfn_start = (virt_to_phys(buff) >> PAGE_SHIFT);

res = remap_pfn_range(vm, vm->vm_start,
                      pfn_start + vm->vm_pgoff, //Add offset to the physical page frame number
                      vm->vm_end - vm->vm_start,
                      vm->vm_page_prot);
  • vm: VMA (virtual memory descriptor) automatically generated by the kernel when mmap is called
  • vm->vm_start: The starting address of the VMA
  • vm->_end: The end address of the VMA
  • virt_to_phys: Convert virtual address to physical address
  • PAGE_SHIFT: Macro, value is 12, 1<
  • vm->vm_pgoff: Refers to the offset on the physical memory of the file during mapping. Only part of the file is mapped. The unit is page (4K)
  • vm->vm_page_prot: The access permissions of this virtual memory are determined by mmap parameters, such as readable, writable, shared, etc.

Reference article:
[Memory mapping function remap_pfn_range learning – example analysis (1)] https://www.cnblogs.com/pengdonglin137/p/8149859.html