Understanding kmap function in Linux Kernel

The kmap system call is used to map a given page structure into the kernel’s virtual address space. The declaration of kmap is as follows:

1
2
3
4
5
static inline void *kmap(struct page *page)
{
might_sleep();
return page_address(page);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* page_address - get the mapped virtual address of a page
* @page: &struct page to get the virtual address of
*
* Returns the page's virtual address.
*/
void *page_address(struct page *page)
{
unsigned long flags;
void *ret;
struct page_address_slot *pas;

if (!PageHighMem(page))
return lowmem_page_address(page);

pas = page_slot(page);
ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;

list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}

The prerequisite to understand these memory mapping concepts is to know the concepts of virtual memory.
For example, in a 32-bit architecture CPU can generate 2^32 addresses i-e. it can virtually address upto 4GB of memory. In general, kernel is assigned 1GB(also called LOW memory) and User space is assigned 3GB(also called HIGH memory).

  1. mmap: Normally user space processes can’t access device memory directly for security purpose. So, user space processes use the mmap system call to ask kernel to map the device into virtual address space of the calling process. After the mapping the user space process can write directly into the device memory via the returned address.

The mmap system call is declared as follows:
mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);

Where as, the mmap field in the driver’s file operation structure is declared as:

int (mmap) (struct file filp, struct vm_area_struct *vma);

You can get the details about these declaration from manuals.

  1. ioremap: ioremap is used to map physical memory into virtual address space of the kernel.
    In most of the system now a days, the devices are memory mapped to the system. That means, kernel can access these device registers by writing directly into these physical memory addresses. But, when MMU is enabled in a system, the kernel works on virtual memory. So, the physical address has to be mapped into virtual address first before the kernel can access these devices and perform IO. ioremap call does exactly that.
    The ioremap declaration is as follows:
    void *ioremap(unsigned long phys_addr, unsigned long size);

  2. kmap: To understand kmap, you need to have some understanding about Memory Management Unit (MMU) and the page tables.

In a 32-bit system, generally a page is of size 4KB. Every physical page is represented by a structure struct page. The mapping of virtual to physical address translation is stored in the page tables. So, when a process does some operation on virtual address, then MMU first translates the virtual address into physical address, and then operation could be performed on actual physical memory.

The pages in low memory is always permanently mapped to kernel address space, but the pages in high memory might not be permanently mapped into the kernel’s
address space. As I have explained before if MMU is enabled, kernel always works on virtual address space. If you have the physical page structure, then first it needs to have a mapping in kernel’s virtual address space and only then we can use it.

void kmap(struct page page);

This function works on both High memory and Low memory.
If the page structure belongs to a page in low memory, then just the virtual address of the page is returned. (Note that Low memory pages already have permanent mappings)

If the page resides in high memory, a permanent mapping is created in the page tables and then the address is returned.

Also note that the permanent mappings are limited, so best programming practice is to unmap High memory mappings when it’s no longer required. This can be done via:
void kunmap(struct page *page);