"Go" address space, kernel space, IO address space

Source: Internet
Author: User
Tags ranges

http://blog.csdn.net/wuxinke_blog/article/details/8769131

Is there a series of questions that are troubling you: what is the scope of the address space in which the user program compiles the connection? What is the scope of the kernel post-compilation address space? What is the address space for I/O to access the peripherals?

Answer the first question first. The most common executable file format for Linux is Elf (executable and Linkableformat). In the elf-formatted executable code, LD always starts the program's "code snippet" from 0x8000000, which is true for every program. As for the actual address of the program execution in physical memory, it is temporarily allocated by the kernel for its memory mapping, depending on the physical memory page allocated at that time.
We can use the Linux utility objdump to disassemble your program to know its address range.

For example: Suppose we have a simple C program hello.c
# include<stdio.h>
Greeting ()
{
printf ("hello,world!\n");
}
Main ()
{
Greeting ();
}
The reason why such a simple program is written in two functions is to illustrate the transfer process of instructions. We compile and connect it with GCC and LD to get the executable code hello. Then, use the Linux utility objdump to disassemble it:
$objdump –d Hello
The main fragments obtained are:
08048568 <greeting>:
8048568:PUSHL? p
8048569:movl%esp,? p
804856B:PUSHL $0x809404
8048570:call 8048474 <_init+0x84>
8048575:addl $0x4,%esp
8048578:leave
8048579:ret
804857A:MOVL%esi,%esi
0804857c <main>:
804857C:PUSHL? p
804857d:movl%esp,? p
804857f:call 8048568 <greeting>
8048584:leave
8048585:ret
8048586:nop
8048587:nop
Among them, like 08048568 such address, is we often say the virtual address (this address real existence, just because of the existence of physical address, it appears to be "virtual").

. virtual memory, kernel space, and user space
The size of Linux virtual memory is 2^32 (on a 32-bit x86 machine), and the kernel divides this 4G byte space into two parts. The highest 1G bytes (from virtual addresses 0xc0000000 to 0xFFFFFFFF) are used by the kernel, known as "kernel space." The lower 3G bytes (from the virtual address 0x00000000 to 0xBFFFFFFF) are used by each process, called "User space." Because each process can enter the kernel through system calls, the Linux kernel space is shared by all processes within the system. Thus, from a specific process point of view, each process can have a virtual address space of 4G bytes (also known as virtual memory).

Each process has its own private user space (0~3G), which is not visible to other processes in the system. The highest 1GB kernel space is shared by all processes and the kernel. In addition, the "User space" of the process is also called the "address space", in the following narrative, we no longer distinguish between the two terms.

User space is not process-shared, but process-isolated. Each process can have a maximum of 3GB of user space. Access to one of the processes by one process does not conflict with the access of other processes to the same address. For example, a process can read an integer 8 from the address 0X1234ABCD of its user space, while another process can read an integer 20 from the address 0X1234ABCD of its user space, depending on the logic of the process itself.

At any one time, only one process is running on a CPU. So for this CPU, at this moment, the whole system only has a 4GB virtual address space, this virtual address space is for this process. The virtual address space also switches as the process transitions. As you can see, each process has its own virtual address space, and the virtual address space is known to the CPU that runs it only when the process is running. At other times, its virtual address space is not known to the CPU. So although each process can have a 4GB virtual address space, only one virtual address space exists in the CPU eye. Changes in the virtual address space as the process switches.

From above we know that the address space formed after a program compiles a connection is a virtual address space, but the program eventually runs in physical memory. Therefore, any virtual address given by the application must eventually be converted to a physical address, so the dummy address space must be mapped to the physical memory space, which needs to be established through the data structure specified by the hardware architecture. This is what we call the section Descriptor table and page tables, and Linux is primarily mapped by page tables.

So, we come to the conclusion that if the given page table is different, then the CPU will convert the address in a virtual address space into a different physical address. So we set up a page table for each process, mapping the virtual address space of each process to the physical address space according to its own needs. Since there is only one process running on a CPU at a time, when the process switches, the page table is also replaced with the page table of the corresponding process, which enables each process to have its own virtual address space without affecting each other. Therefore, at any moment, for a CPU, only need to have the current process of the page table, you can achieve its virtual address to the physical address conversion.

. Kernel space to physical memory mapping
Kernel space for all processes are shared, which holds the kernel code and data, and the process of user space is stored in the user program code and data, whether it is a kernel program or user program, they are compiled and connected, The resulting instruction and symbolic address are both virtual addresses (see the example in Section 2.5), not physical addresses in physical memory.

Although the kernel space occupies up to 1GB bytes in each virtual space, the mapping to physical memory always begins with the lowest address (0x00000000), as shown in 4.2, so that a simple linear mapping between kernel space and physical memory is established. where 3GB (0xC0000000) is the amount of displacement between the physical address and the virtual address, which is called Page_offset in the Linux code.

Let's take a look at the description and definition of address mappings in kernel space in the include/asm/i386/page.h header file:
#define __PAGE_OFFSET (0xC0000000)
......
#define Page_offset ((unsignedlong) __page_offset)
#define __PA (x) ((Unsignedlong) (x)-page_offset)
#define __VA (x) ((void*) ((unsigned long) (x) +page_offset)
For kernel space, given a virtual address X, its physical address is "X-page_offset", given a physical address X, its virtual address is "X+page_offset".
Here again, the macro __PA () simply maps a virtual address of a kernel space to a physical address, but never applies to user space, and the address mapping of the user space is much more complex, and it is done by paging mechanism.

The kernel space is 3GB~4GB, and the 1GB space is divided into the following sections, as shown in 1:

First, the meaning of the symbols in the diagram is described:
page_offset:0xc0000000, i.e. 3GB
High_memory: The literal meaning of this variable is high-end memory, exactly what is high-end memory, the Linux kernel stipulates that the first 896 of RAM is called low-end memory, while 896~1GB total 128MB for high-end memory. If your memory is 512M, then what is the high_memory? Is 3gb+512, that is, the physical address x<=896m, there is the kernel address 0xc0000000+x, otherwise, high_memory=0xc0000000+896m
or high_memory the maximum value is 0xc0000000+896m, the actual value is 0xc0000000+x
In the source code, in the function mem_init, there is a line:
High_memory = (void *) __va (MAX_LOW_PFN * page_size);
Where MAX_LOW_PFN is the maximum number of pages for physical memory.
So in the figure, Page_offset to High_memory is the so-called physical memory mapping. Only between this section is a simple linear relationship between the physical address and the virtual address.
It is also stated that to allocate memory in this memory, the Kmalloc () function is called. Conversely, the physical pages of the memory allocated through KMALLOC () are contiguous.

Vmalloc_start: The starting address of the noncontiguous zone.
Vmalloc_end: The end address of the noncontiguous zone
In a noncontiguous region, there is a 8MB security zone between the end of the physical memory map and the first vmalloc to "capture" out-of-bounds access to memory. For the same reason, insert another 4KB security zone to isolate the noncontiguous zone.

The allocation of noncontiguous zones calls the Vmalloc () function.

Vmalloc () and Kmalloc () are all functions used to allocate memory in kernel code, but what is the difference between them?
As we have seen from the previous introduction, the memory allocated by both functions is in kernel space, that is, from 3GB~4GB, but in different locations, Kmalloc () allocates memory between 3gb~high_memory, which corresponds to the mapping of physical memory one by one, and Vmalloc () The allocated memory between VMALLOC_START~4GB, which is a non-contiguous area of memory mapped to physical memory may also be noncontiguous.
Vmalloc () works similarly to Kmalloc (), where the primary difference is that the physical address assigned by the former does not need to be contiguous, while the latter ensures that the page is physically contiguous (the virtual address is also contiguous).
Although a physically contiguous block of memory is required only in some cases, many kernel code calls Kmalloc () instead of Vmalloc () to get the memory. This is mainly due to performance considerations. The Vmalloc () function must specifically establish a page table entry in order to convert a physically discontinuous page into a contiguous page on a virtual address space. Also, pages obtained through VMALLOC () must be mapped one by one (because they are not physically contiguous), which results in a much larger buffer flush than direct memory mapping. For these reasons, Vmalloc () is used only when absolutely necessary-typically to obtain large chunks of memory, for example, when a module is dynamically inserted into the kernel, loading the module into memory allocated by Vmalloc ().
The Vmalloc () function is relatively simple to use:
Char *buf;
BUF =vmalloc (16*page_size);
if (!BUF)

When you are finished using the allocated memory, be sure to release it:
Vfree (BUF);
1.I/O port and I/O memory

Device drivers have direct access to the physical circuitry on the peripheral or its interface card, which is usually in the form of registers. Peripheral registers, also known as I/O ports, typically include: control registers, status registers, and data registers. Depending on how the peripheral registers are accessed, the CPU can be divided into two main classes. A class of CPUs (such as M68K,POWERPC, etc.) treat these registers as part of memory, registers participate in memory-uniform addressing, access registers are accessed through general memory instructions, so the CPU does not have instructions specifically for device I/O. This is called the "I/O memory" approach. Another class of CPUs (typically, such as X86) treat peripheral registers as a separate address space, so instructions to access the memory cannot be used to access these registers, but to set private instructions for the read/write of the peripheral registers, such as in and out instructions. This is called the "I/O port" approach. However, the "address space" for I/O directives is relatively small. In fact, x86 's I/O address space is now very crowded.

However, with the development of computer technology, the simple I/O port method can not meet the actual needs, because this method can only operate on several registers in the peripheral. In fact, demand is constantly changing, for example, a graphics card can be plugged into a PC, with 2MB of storage space, and possibly even ROM, with executable code installed. Since the advent of the PCI bus, whether it is the design of the CPU I/O port or I/O memory mode, the peripheral card must be mapped to memory space, in fact, the use of virtual storage space means, such a mapping is established by Ioremap ().

2. Accessing the I/O port
In, out, ins, and outs assembly-language directives all have access to I/O ports. The following helper functions are included in the kernel to simplify this access:

INB (), INW (), INL ()
Read 1, 2, or 4 consecutive bytes from the I/O port, respectively. The suffix "B", "w", and "L" represent one byte (8 bits), one word (16 bits), and one long integer (32 bits), respectively.

Inb_p (), inw_p (), inl_p ()
Read 1, 2, or 4 consecutive bytes from the I/O port, and then execute a "dummy (dummy, empty instruction)" instruction to suspend the CPU.

Outb (), OUTW (), Outl ()
Write 1, 2, or 4 contiguous bytes to an I/O port, respectively.

Outb_p (), outw_p (), outl_p ()
Write 1, 2, or 4 consecutive bytes to an I/O port, and then execute a "dummy" instruction to halt the CPU.

INSB (), INSW (), inSL ()
Reads sequentially from the I/O port into a contiguous sequence of bytes in a group of 1, 2, or 4 bytes. The length of the byte sequence is given by the parameters of the function.

OUTSB (), OUTSW (), OUTSL ()
Writes to the I/O port a contiguous sequence of bytes in a group of 1, 2, or 4 bytes, respectively.

Although access to I/O ports is straightforward, it may not be as simple to detect which I/O ports have been allocated to I/O devices, especially for ISA bus-based systems. Typically, the I/O device driver needs to blindly write data to an I/O port in order to probe the hardware device, but the system crashes if other hardware devices already use the port. To prevent this from happening, the kernel must use "resources" to record the I/O ports assigned to each hardware device.

A resource represents a part of an entity that is partially mutually exclusive to the device driver. Here, the resource represents a range of I/O port addresses. The information corresponding to each resource is stored in the resource data structure:
struct Resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent,*sibling, *child;
};
Its fields are shown in table 1. All the same resources are inserted into a tree data structure (father, brother, and child); For example, all resources that represent an I/O port address range are included in a tree with a root node of Ioport_resource.
Table 1:resource fields in the data structure

Type field Description

const char * name
Name of the resource owner

unsigned long
Start
Start of resource scope

unsigned long
End
End of resource scope

unsigned long
Flags
Various signs

struct Resource *
Parent
Pointer to father in the resource tree

struct Resource *
Sibling
Pointer to a sibling in the resource tree

struct Resource *
Child
A pointer to the first child in the resource tree

The child of the node is collected in a linked list, and its first element is pointed to by children. The sibling field points to the next node in the list.

Why use trees? For example, consider the I/O port address used by the IDE hard disk interface-for example, from 0xf000 to 0xf00f. Then, the Start field is 0xf000 and the end field is 0xf00f in the tree, and the general name of the controller is stored in the name fields. However, IDE device drivers need to remember additional information, that is, the IDE chain Master uses 0xf000 to 0xf007 sub-range, from the disk using 0xf008 to 0xf00f sub-ranges. To do this, the device driver inserts two child-scoped children into the resource corresponding to the entire range from 0xf000 to 0xf00f. In general, each node in the tree is definitely equivalent to a child range of the parent node's corresponding range. The root node of the I/O Port resource Tree (Ioport_resource) spans the entire I/O address space (from ports 0 to 65535).

Any device driver can use the following three functions, the parameters passed to them are the root node of the resource tree and the address of the new resource data structure to be inserted:
Request_resource ()
Assigns a given range to an I/O device.
Allocate_resource ()
Find the available range for a given size and arrangement in the resource tree, and if so, assign this range to an I/O device (primarily used by the PCI device driver, which can be configured with any port number and memory address on the motherboard).
Release_resource ()
Releases the given range previously assigned to the I/O device.
The kernel also defines some of the shortcut functions applied to I/O ports for the above functions: Request_region () allocates a given range of I/O ports, and Release_region () frees the ranges previously allocated to I/O ports. The tree that is currently assigned to all I/O addresses for I/O devices can be obtained from the/proc/ioports file.

3. Mapping I/O ports to memory space-another way to access I/O ports
The prototype of the mapping function is:
void *ioport_map (unsigned long port, unsigned int count);
This function allows you to remap the count of successive I/O ports starting with Port to a "memory space". These I/O ports can then be accessed on the address they return as if they were accessing I/O memory.
Note, however, that I/O ports must also be allocated through Request_region () before mapping.

When this mapping is no longer needed, you need to call the following function to undo:
void Ioport_unmap (void *addr);
After the physical address of the device is mapped to the virtual address, although these addresses can be accessed directly through pointers, the engineer should use the following set of functions of the Linux kernel to complete the access I/O Memory: • Read I/O memory
unsigned int ioread8 (void *addr);
unsigned int ioread16 (void *addr);
unsigned int ioread32 (void *addr);

Earlier versions of the functions corresponding to the above functions are (these functions are still supported in Linux 2.6):
unsigned readb (address);
unsigned readw (address);
unsigned readl (address);

• Write I/O memory
void Iowrite8 (U8 value, void *addr);
void Iowrite16 (U16 value, void *addr);
void Iowrite32 (u32 value, void *addr);

Earlier versions of the functions corresponding to the above functions are (these functions are still supported in Linux 2.6):
void Writeb (unsigned value, address);
void Writew (unsigned value, address);
void Writel (unsigned value, address);

4. Accessing I/O memory
The Linux kernel also provides a set of functions to request and release a range of I/O Memory:
struct Resource*requset_mem_region (unsigned long start, unsigned long len,char*name);
This function requests the Len memory address (the virtual address between 3g~4g) from the kernel, where start is the I/O Physical address, and name is the device. Attention. If the assignment succeeds, it returns non-null, otherwise, NULL is returned.

In addition, you can view the memory range of the system to various devices through/PROC/IOMEM.
To release the requested I/O memory, you should use the Release_mem_region () function:
Voidrelease_mem_region (unsigned long start, unsigned long len)
After requesting a set of I/O memory, call the Ioremap () function:
void * IOREMAP (unsigned long phys_addr, unsigned long size,unsigned long flags);
The meanings of three of these parameters are:
PHYS_ADDR: The same I/O Physical address as the parameter start in the Requset_mem_region function;
Size: The amount of space to map;
Flags: Flags to map the IO space and permissions;
Function: Maps an I/O address space to the virtual address space of the kernel (requested by release_mem_region ())
Why do I need to apply for virtual memory before mapping?
————————————————————————————————————
For the reader's consideration: direct access to I/O ports, mapping I/O ports to memory for access, and access to I/O memory, what is the difference between the three, and how is it specifically applied in driver development?

"Go" address space, kernel space, IO address space

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.