Mmap is a common Linux system call API with a wide range of uses, including anonymous shared memory, Binder mechanisms, and more. This paper simply records the process and principle of mMAP invocation in Android. The mMAP function prototype is as follows:
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
Copy the code
Several important parameters
- Parameter start: indicates the start address of the memory to be mapped. It is usually set to NULL, indicating that the system automatically selects the address and returns the address after the mapping is successful.
- Parameter length: represents how large a portion of the file is mapped to memory.
- Parameter prot: indicates the protection mode of the mapping area. It can be a combination of the following ways:
The return value is void *, which is mapped to a virtual memory address after successful allocation.
Mmap is a system call. The user control indirectly triggers the soft interrupt through SWI instruction and enters the kernel state (switch of various environments). After entering the kernel state, the kernel function can be called for processing. mmap->mmap64->__mmap2->sys_mmap2-> sys_mmap_pgoff ->do_mmap_pgoff
/Users/personal/source_code/android/platform/bionic/libc/bionic/mmap.cpp:
/Users/personal/source_code/android/platform/bionic/libc/arch-arm/syscalls/__mmap2.S:
The value of __NR_mmap in the system function call table is as follows:
Through the system call, swI soft interrupt is performed to enter the kernel state, which is finally mapped to the kernel function sys_MMap2 in call.s
Sys_mmap2 finally completes the subsequent logic in kernel state by sys_MMAP_pgoff.
Sys_mmap_pgoff is implemented through macro definitions
/Users/personal/source_code/android/kernel/common/mm/mmap.c:
Then call do_mmap_pgoff:
/Users/personal/source_code/android/kernel/common/mm/mmap.c:
unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff, unsigned long *populate) { struct mm_struct * mm = current->mm; struct inode *inode; vm_flags_t vm_flags; *populate = 0; . <! Addr = get_unmapped_area(file, addr, len, pgoff, flags); . inode = file ? file_inode(file) : NULL; . <! Addr = mmap_region(file, addr, len, vm_FLAGS, pgoff); if (! IS_ERR_VALUE(addr) && ((vm_flags & VM_LOCKED) || (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE)) *populate = len; return addr; }Copy the code
Get_unmapped_area is used to find a memory area for user space.
unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { unsigned long (*get_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); . get_area = current->mm->get_unmapped_area; if (file && file->f_op && file->f_op->get_unmapped_area) get_area = file->f_op->get_unmapped_area; addr = get_area(file, addr, len, pgoff, flags); . return error ? error : addr; }Copy the code
Current ->mm->get_unmapped_area is normally assigned arch_get_unmapped_area_topdown,
unsigned long arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, const unsigned long len, const unsigned long pgoff, const unsigned long flags) { struct vm_area_struct *vma; struct mm_struct *mm = current->mm; unsigned long addr = addr0; int do_align = 0; int aliasing = cache_is_vipt_aliasing(); struct vm_unmapped_area_info info; . addr = vm_unmapped_area(&info); . return addr; }Copy the code
First find the appropriate virtual memory (user space), after several turnover, call the corresponding file or device driver in the MMAP function, complete the device file Mmap, as for how to deal with virtual space, to see each file’s own operation.
There’s a key structure here
const struct file_operations *f_op;
Copy the code
It is the entry point for file-driven operations, and the binding of file_operations is done when open, which is similar to mmap
Get_unused_fd_flags is used to obtain an unused FD, do_file_open is used to create and initialize the file structure, and fd_install is used to bind the FD and file.
Focus on path_openat:
static struct file *path_openat(int dfd, struct filename *pathname, struct nameidata *nd, const struct open_flags *op, int flags) { struct file *base = NULL; struct file *file; struct path path; int opened = 0; int error; file = get_empty_filp(); if (IS_ERR(file)) return file; file->f_flags = op->open_flag; error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base); if (unlikely(error)) goto out; current->total_link_count = 0; error = link_path_walk(pathname->name, nd); if (unlikely(error)) goto out; error = do_last(nd, &path, file, op, &opened, pathname); while (unlikely(error > 0)) { /* trailing symlink */ struct path link = path; void *cookie; if (! (nd->flags & LOOKUP_FOLLOW)) { path_put_conditional(&path, nd); path_put(&nd->path); error = -ELOOP; break; } error = may_follow_link(&link, nd); if (unlikely(error)) break; nd->flags |= LOOKUP_PARENT; nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); error = follow_link(&link, nd, &cookie); if (unlikely(error)) break; error = do_last(nd, &path, file, op, &opened, pathname); put_link(nd, &link, cookie); } out: if (nd->root.mnt && ! (nd->flags & LOOKUP_ROOT)) path_put(&nd->root); if (base) fput(base); if (! (opened & FILE_OPENED)) { BUG_ON(! error); put_filp(file); } if (unlikely(error)) { if (error == -EOPENSTALE) { if (flags & LOOKUP_RCU) error = -ECHILD; else error = -ESTALE; } file = ERR_PTR(error); } return file; }Copy the code
Take the Binder device file as an example. The corresponding file_operations is already registered with the Binder device driver.
To open, you only need the root inode node to retrieve file_operations and call open in file_operations when open succeeds
After open, you can use fd to find file, then use file_operations *f_op in file to call the corresponding driver function, and then look at Mmap.
Binder MMap (One copy)
The most important feature of MMAPS in Binder mechanisms is that interprocess communication can be done in a single copy. The Android application creates a singleton ProcessState object at process startup, and its constructor executes with the Binder MMap to allocate a block of memory for binder communication, as shown below.
ProcessState::ProcessState(const char *driver) : mDriverName(String8(driver)) , mDriverFD(open_driver(driver)) ... { if (mDriverFD >= 0) { // mmap the binder, providing a chunk of virtual address space to receive transactions. mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); . }}Copy the code
The first parameter is the allocation address, 0 means that the system automatically allocates, the process is similar to the previous molecule, first find a suitable virtual memory in user space, then find a suitable virtual memory in kernel space, modify the page table of both controls, so that both map to the same physical memory.
Linux memory is divided into user space and kernel space, and there are two types of page tables, user space page table and kernel space page table. Each process has a user space page table, but the system has only one kernel space page table. The key to Binder MMap is: Also update user space as well as the corresponding page table synchronization mapping the kernel page tables, let two page tables all point to the same address, so that data from A process of user space, direct copy copy to the corresponding kernel space, B and B corresponding kernel space in B the user space also have corresponding mapping process, so you don’t need to copy from the kernel into user space.
static int binder_mmap(struct file *filp, struct vm_area_struct *vma) { int ret; . if ((vma->vm_end - vma->vm_start) > SZ_4M) vma->vm_end = vma->vm_start + SZ_4M; . Area = get_vm_area(vMA ->vm_end - vma->vm_start, VM_IOREMAP); proc->buffer = area->addr; <! Proc ->user_buffer_offset = vMA ->vm_start - (uintptr_t)proc->buffer; . proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL); . <! --> ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vMA); . return ret; }Copy the code
Binder_update_page_range Completes key operations such as memory allocation and page table modification:
static int binder_update_page_range(struct binder_proc *proc, int allocate, void *start, void *end, struct vm_area_struct *vma) { ... <! --> for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { int ret; struct page **page_array_ptr; <! Page = &proc->pages[(page_addr-proc ->buffer)/PAGE_SIZE]; *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO); . <! Ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr); . <! --> user_page_addr = (uintptr_t) page_ADDR + proc->user_buffer_offset; <! --> ret = vm_insert_page(vMA, user_page_addr, page[0]); }... return -ENOMEM; }Copy the code
The key to binder’s copying is that it maps kernel space to user space, which means that the same physical memory can be accessed either in user space using a virtual address or in kernel space using a virtual address.
Principle of mMAP for common files
There are two ways to access common files: first, read/write system access, which allocates a buffer in user space, then enters the kernel, reads the content from disk to the kernel buffer, and finally copies it to user process space, involving at least two data copies. At the same time, multiple processes access a file at the same time, and each process has a copy, resulting in resource waste.
Another is by mmap to access files, mmap () the file directly mapped to the user space, files in the mmap, allocation of memory has not really, only in read/write for the first time will trigger, this time, would trigger a page fault interrupt, when the processing of a page fault, complete memory allocation, but also complete a copy of the file data. In addition, the page table corresponding to user space is modified to complete the mapping from physical memory to user space. In this way, only one data copy exists, which is more efficient. When multiple processes share file data using Mmap at the same time, only one piece of physical memory is needed.
Mmap usage in shared memory
Shared memory is implemented on the basis of a normal mmap file, which is basically a normal Mmap based on the TMPFS file system.