We know that on 32-bit machines, the address space of processes in the Linux operating system is 4 GB, 0-3G is the user space, and 3g-4G is the kernel space. In fact, this 4G address space does not exist, that is, the virtual memory space we call it.
What is the virtual memory space and how it corresponds to the actual physical memory space? Why can we run applications that are larger than the actual physical memory with the virtual memory technology, how does it do it? All of this is a fan. Let's solve the mysteries of happiness step by step!
Let's take a look at what happens to an application after compilation?
For example:
Run the size A. out command to obtain:
Text is the code, data is the initialized global or static variables, and BSS is the uninitialized global or static variables.
For historical reasons, the C program has always been composed of the following parts:
A. Body section. This is the machine instruction part executed by the CPU. Generally, body segments can be shared, so even frequently executed programs (such as text editors, C compilers, and shells) only need one copy in the memory. In addition, body segments are often read-only to prevent programs from modifying their own commands due to accidents.
B. initialize the data segment. This section is usually called a data segment, which contains the variables in the program that need to be assigned an initial value. Example:
Int maxcount = 99; (global variable)
C. Non-initialized data segments. This segment is usually called the BSS segment, which is derived from an operation of an early assembler, meaning "block started by symbol". Before the program starts to run, the kernel initializes this segment to 0. Description outside function:
Long sum [1000];
Store this variable in non-initialized data segments.
D. Stack. The automatic variables and the information to be saved for each function call are stored in this section. Each time a function is called, its return address and caller's environmental information (such as some machine registers) are stored in the stack. Then, the newly called function automatically allocates storage space for it and temporary variables on the stack. By using the stack in this way, the C function can be called recursively.
E. Heap. It is usually used for dynamic storage allocation in the heap. Due to historical practices, the heap is located between the top of the non-initialized Data Segment and the bottom of the stack.
We can see that the stack space is growing down, And the stack space is growing down. Will they meet? Generally, no, because they have a large interval, such:
# Include <stdio. h>
# Include <stdlib. h>
Int bss_var;
Int data_var0 = 1;
Int main ()
{
Printf ("test location: \ n ");
Printf ("\ taddress of main (code segment): % P \ n", main );
Printf ("_________________________________________ \ n ");
Int stack_var0 = 2;
Printf ("Stack location: \ n ");
Printf ("\ tInitial end of stack: % p \ n", & stack_var0 );
Int stack_var1 = 3;
Printf ("\ tNew end of stack: % p \ n", & stack_var1 );
Printf ("_________________________________________ \ n ");
Printf ("Data location: \ n ");
Printf ("\ tAddress of data_var (Data Segment): % p \ n", & data_var0 );
Static int data_var1 = 4;
Printf ("\ tNew end of data_var (Data Segment): % p \ n", & data_var1 );
Printf ("_________________________________________ \ n ");
Printf ("BSS location: \ n ");
Printf ("\ tAddress of bss_var: % p \ n", & bss_var );
Printf ("_________________________________________ \ n ");
Printf ("Heap location: \ n ");
Char * p = (char *) malloc (10 );
Printf ("\ tAddress of head_var: % p \ n", p );
Return 0;
}
The running result is as follows:
Oh, here we see the address, which is a virtual address. How come these addresses come from? In fact, during our compilation, these addresses have been fixed, such as the Red Line.
That is to say, no matter how many times we run the. Out Program, these addresses are the same. We know that the address space of each process in the Linux operating system is independent. In fact, the physical space is independent here. How are they associated with the same virtual address and physical address? Let's continue to explore ....
In the Linux operating system, each process is described by a task_struct struct. The address space of each process is described by a mm_struct. Each segment space in the C language is represented by vm_area_struct, their relationships are as follows:
When running a program, the operating system needs to create a process. What has been done between the Process and the program?
When a program is executed, the content of the program must be placed in the virtual address space of the process, as is the shared library of the executable program. The executable program does not really read the physical memory, but is only linked to the virtual memory of the process.
When an executable program is mapped to the virtual address space of the process, a set of vm_area_struct data structures are generated. Each vm_area_struct data structure represents a part of the impression of executable code, initialization data, and uninitialized data.
The Linux operating system uses sys_exec to map and read executable files. The steps are as follows:
1. Create a group of vm_area_struct;
2. Define a virtual user space and save the start and end addresses (set in the elf segment) to vm_start and vm_end;
3. Save the disk file handle in vm_file;
4. Save the Offset Value of the corresponding segment in the disk file (elf segment has been set) in vm_pgoff;
5. Save the disk operation function that operates the disk file in vm_ops;
Note: There is no corresponding page Directory table item to create a page table, and no page table item is set.
Suppose there is a command in the program that needs to read the content between vm_start -- vm_end.
For example, mov [0x08000011] and % eax will execute the following sequence:
1. The cpu finds the pgd [I] corresponding to the 0x08000011 address based on the current-> pgd. because the content of the pgd [I] remains in the initialization status, that is, it is 0, leading to cpu exceptions.
2. do_page_fault is called. In this function, assign a page table to pgd [I] in the memory and point the table item to it, as shown in:
Note: Here I is 0x08000011 high 10 bits and j is the middle 10 bits. In this case, all pt table items are 0 (pte [j] is also 0 );
3. Allocate a real physical memory page for pte [j] Based on vm_file, vm_pgoff, and vm_ops in vm_area_struct, call filemap_nopage to read the content at the vm_pgoff offset in the disk file to this physical page, as shown in:
① Allocate the physical memory page;
② Read content from the disk file to the physical memory page
As we can see from the above, during process creation, the program content is mapped to the virtual memory space of the process, in order to make a large program run in a limited physical memory space, we can first load the starting part of this program to the physical memory space for running, because the operating system processes the virtual address of the process. If you are converting the virtual address to the physical address, when the physical address does not exist, a page missing exception (nopage) occurs at this time. Then, the operating system will load the data not loaded into the memory on the disk to the physical memory, update the corresponding process page table. Maybe you will ask, what will happen if the physical memory is full at this time?
Next, let's take a look at how the linux operating system handles the problem:
If a process wants to mount a virtual page into the physical memory without any idle physical pages available, the operating system must remove other pages in the physical memory to free up space for this page.
In linux, the physical page is described as follows:
Struct mem_map
{
1. This page uses count. When this page is shared by many processes, the count will be greater than 1.
2. age describes the age on this page and determines whether the page is a good candidate for elimination or exchange.
3. map_nr describe the page frame number of the physical page
}
If the page that is eliminated from the physical memory comes from an image or data file that has not been written, the page does not need to be saved and can be discarded. If a process needs this page, it can retrieve the memory from the image or data file.
However, if the page has been modified, the operating system must keep the content of the page for later access. This type of page is called a "dirty (dirty) page". When it is deleted from the memory, it will be saved in a special file called a swap file.
Compared with the speed of the processor and physical memory, it takes a long time to access the swap file. The operating system must focus on the issue of writing pages to the disk and retrieving the memory when it is used again.
If the algorithm used to determine which page is obsolete or switched is not efficient enough, it may be called "jitter. In this case, the page is always written to the disk and read back, and the operating system is busy and cannot really work.
Linux uses the "Least Recently Used (Least Recently Used, LRU)" Page Scheduling Technique to fairly choose which page can be deleted from the system. In this design system, each page has an "Age", and the age changes as the page is accessed. The more pages are accessed, the younger the page is. The less the page is accessed, the older the page is. Old pages are the best candidate pages for exchange