This series of study notes is basically a blogger’s reading notes for openEuler OPERATING System, with some of the materials I looked up and found useful source code in the middle
The default architecture is ARM
Program and load execution
Unix-like binaries are usually in ELF format, where a logical whole program is stored in several segments based on content type. Main Segment:.text, store machine instruction sequence; .data stores variable global variables and static local variables; . Rodata Stores read-only data and constants; .bss stores uninitialized global variables.
Load is the process by which the operating system reads ELF into memory. First check the ELF header to see if it works. It then reads the end table, gets information about each segment, allocates space for the segments that need to be loaded into memory, and loads those segments into memory.
The execution. The program entry address is stored in the ELF header. After the OS reads the address, it finds the entry in.text and assigns the entry address to PC. The program gains CPU control.
During execution, PC stores the address of the instruction to be executed and LR (the link register) stores the address of the next instruction after the return of the function call.
Each running function has a stack frame that builds a separate context for each invocation of the function. When a function calls a new function, the new function is assigned a new stack frame. After the function is finished, the stack frame will be popped up, and the system will take the saved FP and LR values from the following stack frame (that is, the stack frame of the function that initiated the call to the function that just ended) and continue to run
Process Description
The operating system uses PCB (Process Control Block) to describe the Process. The operating system senses the Process through PCB.
PCB
A PCB is defined in include/ Linux /sched.h struct task_struct, which is a 620-line structure.
When starting a program, the operating system first creates a PCB, and then manages and controls the process according to the information. After the program runs, the system releases the PCB. The main information includes description, control, CPU context, and resource management information
Description information
-
Process identifiers, which the OS uses to mark each process;
-
User ID that identifies the user to which a process belongs
kuid_t loginuid; unsigned int sessionid; Copy the code
-
Family relationship, indicating the relationship of the process to its parent, ancestor, child, sibling, and so on
/* * Pointers to the (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->real_parent->pid) */ /* Real parent process: */ struct task_struct __rcu *real_parent; /* Recipient of SIGCHLD, wait4() reports: */ struct task_struct __rcu *parent; /* * Children/sibling form the list of natural children: */ struct list_head children; struct list_head sibling; struct task_struct *group_leader; Copy the code
Real_parent is the process that created the process, and parent is the parent process associated with the response signal — SIGCHLD, for example, is sent to parent. The reason for this design is that in some cases the true parent may terminate first, so that other processes such as init become the new parent without changing the value of the true parent.
Control information
-
Process status (ready, blocked, running, terminated)
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ /* The related macros are defined as */ /* Used in tsk->state: */ #define TASK_RUNNING 0x0000 #define TASK_INTERRUPTIBLE 0x0001 #define TASK_UNINTERRUPTIBLE 0x0002 #define __TASK_STOPPED 0x0004 #define __TASK_TRACED 0x0008 #define TASK_PARKED 0x0040 #define TASK_DEAD 0x0080 #define TASK_WAKEKILL 0x0100 #define TASK_WAKING 0x0200 #define TASK_NOLOAD 0x0400 #define TASK_NEW 0x0800 #define TASK_STATE_MAX 0x1000 Copy the code
-
Process priority
int prio; int static_prio; int normal_prio; unsigned int rt_priority; Copy the code
- Static priority: specifies when the process is started. The smaller the value, the higher it is. Usually not, but system calls are available
nice()
Modified; - Dynamic priority: it can be changed temporarily due to the change of scheduling policies.
- Real-time: The finite degree is only related to the real-time priority, the smaller the value, the higher the value. Real-time process scheduling is always better than normal process scheduling
- Static priority: specifies when the process is started. The smaller the value, the higher it is. Usually not, but system calls are available
-
2. To record the process’s possession and utilization of resources on the basis of which scheduling (such as deprivation) is carried out, such as in terms of time and context switching:
u64 utime; u64 stime; #ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME u64 utimescaled; u64 stimescaled; #endif u64 gtime; struct prev_cputime prev_cputime; #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN struct vtime vtime; #endif #ifdef CONFIG_NO_HZ_FULL atomic_t tick_dep_mask; #endif /* Context switch counts: */ unsigned long nvcsw; unsigned long nivcsw; /* Monotonic time in nsecs: */ u64 start_time; /* Boot based time in nsecs: */ u64 real_start_time; Copy the code
CPU context
CPU context refers to the value in each CPU register at a certain time, which is used to save the state during process switching. It is stored in task_struct->thread_struct->cpu_context
As you can see from the following code, CPU_context is a collection of values in each register
/* arch/arm64/include/asm/processor.h */
struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21;
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp;
unsigned long sp;
unsigned long pc;
};
struct thread_struct {
struct cpu_context cpu_context; /* cpu context */
/* The thread_struct structure contains all cpu-related state information */
};
Copy the code
Resource Management Information
This part of information accounts for the largest proportion in PCB, including memory, file system, IO device information, mainly related to memory and files.
void *stack; // Point to the kernel stack of the process. The kernel stack is the stack used by the process in the kernel state, in the kernel space
// User space descriptor for the process
struct mm_struct *mm;
struct mm_struct *active_mm;
/* Filesystem information: */
struct fs_struct *fs; // The file system associated with the process
/* Open file information: */
struct files_struct *files; // List of files the process is opening
// Memory descriptor, defined in include/ Linux /mm_types.h
struct mm_struct {
spinlock_t arg_lock; /* Protect the below fields */
// Describe the starting position of each segment in memory, including stack, mapping segment, heap, BSS segment, data segment, code segment
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
/ /...
};
// File system information associated with the process, include/ Linux /fs_struct.h
struct fs_struct {
int users; // The number of referenced users of this structure
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root.pwd; // Root and current directory
} __randomize_layout;
/* include/linux/fdtable.h * Open file table structure */
struct files_struct {
/* * read mostly part */
atomic_t count; // Reference count
struct fdtable __rcu *fdt; // By default points to FDT, which can be used to dynamically allocate memory
struct fdtable fdtab; // Provide an initial value for FDT
// FDT and FDtable are used to manage file descriptors
// ...
};
// path. include/linux/path
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
} __randomize_layout;
struct fdtable {
unsigned int max_fds;
struct file __rcu支那fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
Copy the code
Process status
The current state of the process is described by the state value in the PCB. The states here are run, ready, blocked, terminated (zombie, dead)
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
Copy the code
- Ready: The process is in the run queue, has obtained all resources except CPU, and is waiting for the OS to select CPU
- Running: If the current process is abandoned or preempted, it is ready. The current process is blocked while waiting for resources or events. Run finished, enter termination state (zombie/waiting)
- Blocking: Usually waiting for an external event (such as I/O) to arrive. However, it can be woken up by system calls, signals, etc. (Blocking can be mild to medium depth, different levels need different conditions)
- Termination:
- Zombie: The parent process does not reclaim the process and its occupied resources (including PCBS). If the parent process terminates first, init becomes the parent of the child process and recycles resources after the parent process completes
- Death: The resource is reclaimed by the parent process