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:
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;
}
Here is a explanation: static
the 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 id
variables to receive fork
the 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 fork
to create a child process, the child process prints every second pid
ppid
g_val
g_val
, and g_val
changes 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_val
creates space for the child process! When printing, the value of the child process g_val
is equal to 200, and the value of the parent process g_val
is equal to 100. no problem! But we found that after copy-on-write occurs, the printouts of the parent and child processes &g_val
are the same!
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_struct
In this structure, there is a field calledmm_struct*
, through which the virtual address (linear address) of the parent process can be found. When we define a global variable,g_val
a virtual address will be assigned to this variable! There is something called a page table inLinux
the operating system. You can understand it as a pagemap
table. 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 processtask_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:
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_val
copy-on-write??
The code and data of the parent and child processes are shared. When we g_val
make modifications to in the child process, the operating system will re-open g_val
the airdrop of the type size for the child process, and then g_val
copy it to this new space and modify g_val
the value. , last modified g_val
physical address of virtual address mapping! Complete copy-on-write!
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 fork
after id
have two values?
fork()
The functionreturn
creates the child process before!return
The essence is also writing! In other words, copy-on-write will occur! Although the parent and child processes are accessing the same oneid
, they access different physical addresses (physical memory) according to the page table. This isid
why one variable can read two values!
address space
Taking the operating system 32
of bits Linux
as 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:
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 Linux
if the source code of the kernel looks like this:
We can see that task_struct
there is a in struct mm_struct
, jump to mm_struct
inside, we see start_code
end_code
fields such as this, proving that the conclusion we have drawn is that there is no questionable!
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_struct
we have to maintain the location of the process's code and data in the memory. Once the process status switches, task_struct
the 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!
As shown in the figure, there is a piece of code in the code area. The virtual address is and 0x11
the physical address is. 0x22
There 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 CPU
in and find that we want to modify the physical memory cr3
with the permission flag bit , and the operating system will directly intercept it! r
The 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?
cr3
Why 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:
Linux
The 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 PCB
in physical memory!
However, Linux
there 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
5MB
of code and data! So the process started running! It turned out that during the process of scheduling this process, only1MB
the code and data of were used, but when the executable program was loaded,5MB
wow! 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 CPU
through cr3
the 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_struct
is maintained intask_struct
,cr3
the register points to the page table of the current process, andcr3
the 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?