Linux process address space

Knowledge review

In the process of learning C language, we know that memory can be divided into stack area, heap area, global data area, character constant area, and code area. His spatial arrangement may be as follows:
Insert image description here
Among them, the global data area can be divided into an initialized global data area and an uninitialized global data area!

  • The stack area grows downward. (One form of expression: in the same stack frame, the address of the variable defined first is higher, and the address of the variable defined later is lower)
  • The heap grows upward. (As the heap area is used, the address of the variable grows upward!)

The area between stacks is called the shared area. This will be explained in detail in the dynamic and static libraries!
Kernel space: This is the code and data of the operating system!


We can write a simple code to verify it:

#include<stdio.h>
#include<stdlib.h>

int g_val = 10;

int main()
{
    
    
    int a = 1;
    int b = 2;
    char* str = "hello linux";
    int* heap1 = (int*)malloc(sizeof(int));

    printf("栈区先定义的变量: &a: %p\n", &a);
    printf("栈区后定义的变量: &b: %p\n", &b);
    printf("堆区定义的变量: &heap1: %p\n", &heap1);
    printf("全局数据: &g_val_init: %p\n", &g_val);
    printf("字符常量区: &str: %p\n", str);

    return 0;
}

Insert image description here
Here is a explanation: staticthe modified local variable can only be initialized once, the scope is local, but the life cycle is the same as the global variable. That is because the local variable is defined in the global data area during compilation. You can print it by printing See the address of the static variable!

Process address space introduction

After we learn about the process address space, we can solve a problem raised in the section on process creation:Using idvariables to receive forkthe return value of a function, why can one variable read two different values?


Let's look at a piece of code: define a global variable g_val = 100, use forkto create a child process, the child process prints every second pid ppid g_val g_val, and g_valchanges it to 200 after 5 seconds; the parent process prints every second pid g_val &g_val. Let’s see what we can observe:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int g_val = 100;

int main()
{
    
    
    pid_t id = fork();
    if(id == 0)
    {
    
    
        //这是子进程
        int cnt = 5;
        while(1)
        {
    
    
            printf("I am child process, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            if(cnt) cnt--;
            else
            {
    
    
                g_val = 200;
                printf("child process change g_val: 100->200\n");
                cnt--;
            }
        }
    }
    else if(id > 0)
    {
    
    
        while(1)
        {
    
    
            printf("I am parent, pid: %d, g_val: %d, &g_val: %p\n", getpid(), g_val, &g_val);
            sleep(1);
        }
    }
    else
    {
    
    
        perror("fork():");
    }

    return 0;
}

After 5 seconds, the child process modifies the global variables g_val, and the child process modifies the shared data of the parent and child processes, resulting in copy-on-write, which g_valcreates space for the child process! When printing, the value of the child process g_valis equal to 200, and the value of the parent process g_valis equal to 100. no problem! But we found that after copy-on-write occurs, the printouts of the parent and child processes &g_valare the same!
Insert image description here
How is it possible that the same variable and the same address can be read at the same time and read different contents?
Therefore, we come to a conclusion: the address printed here is definitely not a physical address. We call this printed addressvirtual addressoror linear address


The pointers and addresses we use when writing C/C++programs are actually not physical addresses!

What is process address space

Let’s not talk about the process address space first. Let’s look at it first.How do the parent and child processes share code and data when the child process is created!

  • First of all, the parent process has its own. task_structIn this structure, there is a field called mm_struct*, through which the virtual address (linear address) of the parent process can be found. When we define a global variable, g_vala virtual address will be assigned to this variable! There is something called a page table in Linuxthe operating system. You can understand it as a page maptable. It stores the mapping relationship between virtual addresses and physical addresses! In other words, through the page table, we can access the physical address (physical memory) through the virtual address!
  • fork()Creating a child process will create a copy for the child process task_struct, copy the virtual memory of the parent process, and copy the page table of the virtual memory!
    • Copy the virtual memory of the parent process: Then, the virtual address of this global variable is the same in the parent and child processes!
    • Copy the page table of the parent process: Then, through the same virtual address and the same page table, you can access the same physical memory!

The above discussion proves that the father-child process data is shared! The principle of code sharing is the same!
We can draw a schematic diagram:
Insert image description here
This schematic diagram can help everyone understand virtual addresses and physical addresses. It is wrong to draw the page table like this. We will explain the real structure of the page table in detail later! However, there is no big problem in understanding the page table in this structure!


How does the child process achieve g_valcopy-on-write??
The code and data of the parent and child processes are shared. When we g_valmake modifications to in the child process, the operating system will re-open g_valthe airdrop of the type size for the child process, and then g_valcopy it to this new space and modify g_valthe value. , last modified g_valphysical address of virtual address mapping! Complete copy-on-write!
Insert image description here
The essence of copy-on-write is to re-open space, but during this process, the virtual address on the left is 0 aware, and the virtual address does not care and will not be affected!


Now we can solve the remaining problems:Why does forkafter idhave two values?

  • fork()The function returncreates the child process before! returnThe essence is also writing! In other words, copy-on-write will occur! Although the parent and child processes are accessing the same one id, they access different physical addresses (physical memory) according to the page table. This is idwhy one variable can read two values!

address space

Taking the operating system 32of bits Linuxas an example,The address space is the address range formed by the combination of addresses: [ 0 , 2 32 ) [0, 2^{32})[0,232)
In a 32-bit operating system, there are 32-bit address bus and data bus. The CPU and memory are connected through the system bus. Each bus has two states: 0 and 1 (high level, low level, so there are 32 buses, There are 2 32 2^{32}232 combinations! Through the bus connecting the CPU to the memory, the CPU can read the data in the memory! 2 32 2^{32}232 corresponds to4 GB 4GB4 GB memory size! So I want to map out4 GB 4GB4 GB of physical memory requires the same range of virtual memory!


How to understand the regional division of address space

The memory area division we saw at the beginning of this article is the address space area division! We also know that the address space is the address range formed by the address arrangement group! The essence of the address space is a data structure object of the kernel, and the address space must also be managed by the operating system!
Therefore, we can divide the address space into regions in a relatively simple way:
Insert image description here
as long as we maintain two pointers to mark the start address and end address of a region division, we are dividing the address space!
After dividing the virtual address space in this way, we can roughly judge whether an out-of-bounds access occurs when accessing a variable!

The smallest unit in the address space is an address, and this address can be used directly!


In order to test the correctness of our conclusion, you can see Linuxif the source code of the kernel looks like this:
We can see that task_structthere is a in struct mm_struct, jump to mm_structinside, we see start_code end_codefields such as this, proving that the conclusion we have drawn is that there is no questionable!
Insert image description here

Why is there a process address space?

Let all processes view memory from a unified perspective

Each process has its own address space, and the virtual address in the address space accesses physical memory through page table mapping!
This is the logic for any process to access physical memory! The way to access memory has been unified!
If there is no process address space, task_structwe have to maintain the location of the process's code and data in the memory. Once the process status switches, task_structthe contents in will be modified, which is too troublesome!

Protect physical memory

After increasing the process address space, if we want to access physical memory, we must convert the page table. During this conversion process, we can review the addressing. Once an access exception occurs, the operating system can intercept it in time! This prevents the request from reaching the memory and effectively protects the physical memory.
In fact, the page table also maintains a field indicating the read and write permissions to the physical memory!
Insert image description here
As shown in the figure, there is a piece of code in the code area. The virtual address is and 0x11the physical address is. 0x22There is a mapping relationship between them through the page table! The code permission flags in the code area are all r. When we try to modify the code area, we find the page table based on the register CPUin and find that we want to modify the physical memory cr3with the permission flag bit , and the operating system will directly intercept it! rThe security of physical memory is guaranteed!


Based on the above phenomenon, we can draw the conclusion:

  • ==Physical memory has no concept of permission management at all! == Otherwise, how is the executable program loaded into memory? cr3Why can registers directly access physical memory without permission checks?
  • Why is the code read-only? Loading a program into memory means writing to physical memory! Physical memory itself is not read-only! Therefore, when the virtual address is mapped to the physical address through the page table, the permission flag in the page table is r, that is, read-only! When we make changes to the code area, the operating system can intercept based on the permission flag! That’s why it’s said that the code area is read-only!

Realize the decoupling of process management module and memory management module

  • Virtualization: The process address space provides an abstraction for virtualization, making each process think it owns the entire system's memory. This allows processes to run independently without caring about the memory layout of other processes.
  • Separate page tables: Each process has its own page table, which is responsible for mapping its virtual address space to physical addresses. In this way, different processes can have different page tables, achieving isolation of memory space. The management of page tables becomes the responsibility of the memory management module.
  • Lazy loading: LinuxThe operating system can adopt a lazy loading strategy and load part of the address space of the process into physical memory only when needed. This paging and lazy loading approach makes memory management more flexible.

The address spaces between processes are isolated, and the memory operations of one process will not directly affect other processes. This helps improve system security and stability.
In other words, processes can have exactly the same virtual address, but can access different physical addresses based on the same virtual address!

Lazy loading of programs

I don’t know if you all have a question: the download of our finished computer game may be dozens of GB, but our physical memory is only that big! How do you get the game running?

When learning the status of a process, we know that there is a suspended state in the operating system discipline. When a process is in a suspended state, its code and data will be placed on the disk and only retained PCBin physical memory!
However, Linuxthere is no suspend state in the operating system. How to know whether the code and data of the current process are in the memory?

  • First, we need to reach a consensus:Modern operating systems rarely do things that waste time and space!
  • Suppose we have an executable program with 20 MB of code and data. When loading the executable program, we load 20 MB 5MBof code and data! So the process started running! It turned out that during the process of scheduling this process, only 1MBthe code and data of were used, but when the executable program was loaded, 5MBwow! So there is a waste of memory airborne! This is not allowed! ! Because in modern operating systems, there is almost nothing that wastes time and space!

In fact, there is a field in the page table that is used to identify whether the code and data of a certain virtual address are loaded into memory! For example, if the flag is 1, it means that the code and data have been loaded into the memory!
When we execute the code, we find the page table CPUthrough cr3the register and find that the virtual address has not loaded the code, which will causepage fault interrupt, if a page fault occurs in the process, some code and data will be loaded again, and memory space will be allocated first. Then fill the physical address to the corresponding virtual address, and you can continue to execute the code!

Precisely because of the existence of page tables and virtual memory, the process does not need to worry about memory! When a page fault occurs, the operating system will automatically call the relevant functions of the memory management module! This also achieves the decoupling of process management and memory management!

Now we can answer the above questions! No matter how much code and data you have, when loading the executable program, I only load a little bit, and the remaining code and data just fill the virtual address in the page table. When we want to access these unloaded code and data, it will trigger Page fault interrupt! Load some code and data again! This also improves memory usage efficiency!

knowledge consolidation

  • What is a process? After learning this, there is another part of the kernel data structure!
    Process = kernel data structure (taskstructmmstruct page table) + code and data Process = kernel data structure (task_struct mm_struct page table) + code and dataprocess=Kernel data structure ( t a s kstructmmst r u c t page table )+code and data

  • Process switching: Because mm_structis maintained in task_struct, cr3the register points to the page table of the current process, and cr3the contents of the register belong to the context of the process! Therefore, the process address space and page table are automatically switched!

  • There is independence between processes! How did you do it?

    • Each process has its own independent kernel data structure.
    • The virtual address of each process can be exactly the same, as long as the physical address mapped through the page table is different! Achieved decoupling of code and data levels! The release of process resources will not affect other processes!

    Therefore, it does not matter where in physical memory a process's code and data are loaded

Summary of knowledge points:

  • What is process address space
  • Why do we need an address space?
    Insert image description here

Guess you like

Origin blog.csdn.net/m0_73096566/article/details/134885379