Transferred from: http://blog.csdn.net/junllee/article/details/7415732
Memory mapping
For processors that provide hardware support such as the MMU (Storage Manager, secondary operating system for memory management, and virtual address translation), Linux provides a sophisticated storage management system that allows the process to access up to 4GB of memory.
The 4GB memory space of a process is artificially divided into two parts-user space and kernel space. User-space addresses are distributed from 0 to 3GB (page_offset, which equals 0xc0000000 in 0x86), and 3GB to 4GB for kernel space, such as:
In kernel space, the address from 3G to Vmalloc_start is the physical memory map area (which contains the kernel image, the physical page frame table Mem_map, and so on), such as the VMware Virtual system memory We use is 160M, then 3g~3g+ 160M This memory should be mapped to physical memory. After the physical memory map area, it is the Vmalloc area. For 160M Systems, the Vmalloc_start position should be near 3g+160m (there is also a 8M gap between the physical memory map area and the Vmalloc_start to prevent the leap), and the vmalloc_end position is close to 4G ( The final position system retains a 128k size area for dedicated page mappings, such as:
Kmalloc and Get_free_page application memory is located in the physical memory map area, but also physically continuous, they have a fixed offset from the real physical address, so there is a simpler conversion relationship, Virt_to_phys () The kernel virtual address can be converted to a physical address:
#define __PA (x) ((unsigned long) (x)-page_offset)
extern inline unsigned long virt_to_phys (volatile void * address)
{
return __pa (address);
}
The above conversion process is to subtract 3G (page_offset=0xc000000) from the virtual address.
The corresponding function is Phys_to_virt (), which translates the internal nuclear physics address into a virtual address:
#define __VA (x) ((void *) ((unsigned long) (x) +page_offset)
extern inline void * PHYS_TO_VIRT (unsigned long address)
{
return __va (address);
}
Both Virt_to_phys () and Phys_to_virt () are defined in include\asm-i386\io.h.
The memory requested by Vmalloc is located between Vmalloc_start~vmalloc_end, and there is no simple conversion relationship with the physical address, although they are logically contiguous, but physically they do not require continuous.
We use the following procedure to demonstrate the differences between Kmalloc, Get_free_page, and Vmalloc:
#include
#include
#include
Module_license ("GPL");
unsigned char *pagemem;
unsigned char *kmallocmem;
unsigned char *vmallocmem;
int __init mem_module_init (void)
{
It is best to check the success of the application every time the memory request
The following code is not checked for demonstration purposes only
Pagemem = (unsigned char*) get_free_page (0);
PRINTK ("Pagemem addr=%x", Pagemem);
Kmallocmem = (unsigned char*) kmalloc (100, 0);
PRINTK ("Kmallocmem addr=%x", Kmallocmem);
Vmallocmem = (unsigned char*) vmalloc (1000000);
PRINTK ("Vmallocmem addr=%x", Vmallocmem);
return 0;
}
void __exit mem_module_exit (void)
{
Free_page (PAGEMEM);
Kfree (KMALLOCMEM);
Vfree (VMALLOCMEM);
}
Module_init (Mem_module_init);
Module_exit (Mem_module_exit);
Our system has 160MB of memory space, run the above program, found Pagemem address in 0xc7arrayarray7000 (about 3g+121m), Kmallocmem address in 0xcarraybc1380 (about 3g+155m), The address of the Vmallocmem is at 0xcabeb000 (about 3g+171m) and conforms to the memory layout described earlier.
Next, we discuss how the Linux device drives the I/O port (register) of the peripheral.
Almost every peripheral is performed by a register on a read-write device, usually including a control register, a status register, and a data register, where the registers of the peripherals are often continually addressed. Depending on the CPU architecture, there are two ways that the CPU addresses the IO port:
(1) I/O mapping mode (i/o-mapped)
Typically, such as the X86 processor specifically implements a separate address space for peripherals, called "I/O address space" or "I/O port space," the CPU accesses address units in this space through specialized I/O directives such as X86 's in and out directives.
(2) Memory mapping mode (memory-mapped)
The CPU of a RISC instruction system (such as ARM, PowerPC, etc.) typically implements only one physical address space, and the peripheral I/O port becomes part of the memory. At this point, the CPU can access the peripheral I/O port as if it were accessing a memory unit without having to set up specialized peripheral I/O instructions.
However, the difference in hardware implementation is completely transparent to the software, and driver developers can treat the I/O port and peripheral memory as a "I/O Memory" resource in the memory-mapped way.
In general, when the system is running, the physical address of the peripheral's I/O memory resources is known and is determined by the hardware design. However, the CPU usually does not have a predefined virtual address range for the physical address of these known peripheral I/O memory resources, and the driver cannot access the I/O memory resources directly through the physical address, but must map them into the core virtual address space (via the page table) before the core virtual address ranges obtained from the mappings are Access these I/O memory resources through an inbound instruction. Linux declares the function Ioremap () in the IO.h header file to map the physical address of the I/O memory resource to the core virtual address space (3GB-4GB), with the following prototype:
void * IOREMAP (unsigned long phys_addr, unsigned long size, unsigned long flags);
The Iounmap function is used to cancel the mapping made by Ioremap () as follows:
void Iounmap (void * addr);
Both of these functions are implemented in the Mm/ioremap.c file.
After the physical address of the I/O memory resource is mapped to a core virtual address, we can theoretically read and write the I/O memory resources as read and write Ram. To ensure cross-platform portability of drivers, we should use specific functions in Linux to access I/O memory resources rather than through pointers to the core virtual addresses. As on the x86 platform, the functions of read/write I/O are as follows:
#define READB (Addr) (* (volatile unsigned char *) __io_virt (addr))
#define READW (Addr) (* (volatile unsigned short *) __io_virt (addr))
#define READL (Addr) (* (volatile unsigned int *) __io_virt (addr))
#define WRITEB (B,ADDR) (* (volatile unsigned char *) __io_virt (addr) = (b))
#define WRITEW (B,ADDR) (* (volatile unsigned short *) __io_virt (addr) = (b))
#define Writel (B,ADDR) (* (volatile unsigned int *) __io_virt (addr) = (b))
#define MEMSET_IO (A,b,c) memset (__io_virt (a), (b), (c))
#define MEMCPY_FROMIO (A,b,c) memcpy ((a), __io_virt (b), (c))
#define MEMCPY_TOIO (A,b,c) memcpy (__io_virt (a), (b), (c))
Finally, we want to emphasize the implementation of the MMAP function in the driver. Mapping a device with mmap means that a segment of the user's space is associated with the device's memory, which allows the program to read or write within the allocated address range, which is actually access to the device.
The author in the Linux source code contains "Ioremap" The text of the search, found that the actual occurrence of the ioremap is quite a few places. So I traced the source to find the physical address of the I/O operation to the virtual address of the real place, found that Linux has alternative ioremap statements, but this conversion process is indispensable.
For example, once again, we pick up s3c2410, a small segment of the ARM chip RTC (Real clock) driver:
static void Get_rtc_time (int alm, struct rtc_time *rtc_tm)
{
SPIN_LOCK_IRQ (&rtc_lock);
if (ALM = = 1) {
Rtc_tm->tm_year = (unsigned char) almyear & msk_rtcyear;
Rtc_tm->tm_mon = (unsigned char) Almmon & Msk_rtcmon;
Rtc_tm->tm_mday = (unsigned char) Almday & Msk_rtcday;
Rtc_tm->tm_hour = (unsigned char) Almhour & Msk_rtchour;
Rtc_tm->tm_min = (unsigned char) almmin & msk_rtcmin;
Rtc_tm->tm_sec = (unsigned char) almsec & msk_rtcsec;
}
else {
Read_rtc_bcd_time:
Rtc_tm->tm_year = (unsigned char) bcdyear & msk_rtcyear;
Rtc_tm->tm_mon = (unsigned char) Bcdmon & Msk_rtcmon;
Rtc_tm->tm_mday = (unsigned char) Bcdday & Msk_rtcday;
Rtc_tm->tm_hour = (unsigned char) Bcdhour & Msk_rtchour;
Rtc_tm->tm_min = (unsigned char) bcdmin & msk_rtcmin;
Rtc_tm->tm_sec = (unsigned char) bcdsec & msk_rtcsec;
if (rtc_tm->tm_sec = = 0) {
/* Re-read all BCD registers in case of Bcdsec is 0.
See RTC sections at the manual for more info. */
Goto Read_rtc_bcd_time;
}
}
SPIN_UNLOCK_IRQ (&rtc_lock);
Bcd_to_bin (rtc_tm->tm_year);
Bcd_to_bin (Rtc_tm->tm_mon);
Bcd_to_bin (Rtc_tm->tm_mday);
Bcd_to_bin (Rtc_tm->tm_hour);
Bcd_to_bin (rtc_tm->tm_min);
Bcd_to_bin (RTC_TM->TM_SEC);
/* The epoch of tm_year is 1array00 */
Rtc_tm->tm_year + = rtc_leap_year-1array00;
/* Tm_mon starts at 0, but RTC Month starts at 1 */
rtc_tm->tm_mon--;
}
I/O operations seem to operate on almyear, Almmon, almday-defined registers, what exactly are these macros defined as?
#define Almday BRTC (0x60)
#define Almmon BRTC (0x64)
#define Almyear BRTC (0x68)
With the help of macro BRTC, this macro is defined as:
#define BRTC (NB) __reg (0x57000000 + (NB))
With the help of the macro __reg, __reg is also defined as:
# define __REG (x) io_p2v (x)
The final io_p2v is the place to actually "play" virtual addresses and physical address conversions:
#define IO_P2V (x) ((x) | 0xa0000000)
There is a __preg corresponding to the __reg:
# define __PREG (x) io_v2p (x)
There is a io_v2p corresponding to the IO_P2V:
#define IO_V2P (x) ((x) & ~0xa0000000)
It can be seen that there is no ioremap is secondary, the key problem is that there is no virtual address and physical address conversion!
The following program retains a memory when it is started, and then uses IOREMAP to map it to the kernel virtual space, while using Remap_page_range to map to the user's virtual space, so that the kernel and the user can access it. If this memory is initialized in the kernel virtual address string "ABCD", then the user virtual address can be read out:
/************mmap_ioremap.c**************/
#include
#include
#include
#include
#include/* for MEM_MAP_ (un) reserve */
#include/* for Virt_to_phys */
#include/* for Kmalloc and Kfree */
Module_parm (Mem_start, "I");
Module_parm (mem_size, "I");
static int mem_start = 101, mem_size = 10;
static char *reserve_virt_addr;
static int major;
int Mmapdrv_open (struct inode *inode, struct file *file);
int mmapdrv_release (struct inode *inode, struct file *file);
int mmapdrv_mmap (struct file *file, struct vm_area_struct *vma);
static struct File_operations Mmapdrv_fops =
{
Owner:this_module, Mmap:mmapdrv_mmap, Open:mmapdrv_open, Release:
Mmapdrv_release,
};
int init_module (void)
{
if ((major = Register_chrdev (0, "Mmapdrv", &mmapdrv_fops)) Vm_pgoff vm_end-vma->vm_start;
if (Size > Mem_size *1024 * 1024)
{
PRINTK ("Size too big\n");
Return (-Enxio);
}
Offset = offset + Mem_start * 1024 * 1024;
/* We don't want to having this area swapped out, lock it */
Vma->vm_flags |= vm_locked;
if (Remap_page_range (VMA, Vma->vm_start, offset, size, page_shared))
{
PRINTK ("Remap page range failed\n");
Return-enxio;
}
return (0);
}
The function of the Remap_page_range function is to construct a new page table for mapping a physical address, which implements the mapping between kernel space and user space, with the following prototype:
int Remap_page_range (vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot);
The most typical example of using mmap is the driver of the display card, which maps the memory space directly from the kernel to the user space to provide the read/write efficiency of the memory.
Ioremap and Mmap "Turn"