1: Relevant information
Code: linux 0.11
2: Process structure
Each process corresponds to a struct task in the kernel_ Struct, which is used to represent the status and related information of the process
struct task_struct { /* these are hardcoded - don't touch */ long state; /*Process status - 1 runnable, 0 runnable, > 0 stopped */ long counter; //The time slice count of the process. When it is 0, scheduling switching is required. counter = counter/2 + priority long priority;//The greater the priority, the higher the priority, and the priority will be implemented long signal;//signal struct sigaction sigaction[32];//Signal bitmap long blocked; /* bitmap of masked signals Blocking and non blocking*/ /* various fields */ int exit_code;//Exit code unsigned long start_code,end_code,end_data,brk,start_stack;//Start code end code long pid,father,pgrp,session,leader;//Process ID unsigned short uid,euid,suid;//User ID unsigned short gid,egid,sgid;//Group ID long alarm; //warning long utime,stime,cutime,cstime,start_time; //User running time, kernel running time, sub process user running time, sub process kernel running time, start time unsigned short used_math;//Use coprocessor /* file system info */ int tty; /* -1 if no tty, so it must be signed Develop console */ unsigned short umask; struct m_inode * pwd;//route struct m_inode * root;//root struct m_inode * executable; unsigned long close_on_exec; struct file * filp[NR_OPEN];//Records the files opened by the current process /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */ struct desc_struct ldt[3];//Address where codes and data are stored /* tss for this task */ struct tss_struct tss; //Store and record the value of the current CPU register }; struct tss_struct { long back_link; /* 16 high bits zero */ long esp0; long ss0; /* 16 high bits zero */ long esp1; long ss1; /* 16 high bits zero */ long esp2; long ss2; /* 16 high bits zero */ long cr3; long eip; long eflags; long eax,ecx,edx,ebx; long esp; long ebp; long esi; long edi; long es; /* 16 high bits zero */ long cs; /* 16 high bits zero */ long ss; /* 16 high bits zero */ long ds; /* 16 high bits zero */ long fs; /* 16 high bits zero */ long gs; /* 16 high bits zero */ long ldt; /* 16 high bits zero */ long trace_bitmap; /* bits: trace 0, bitmap 16-31 */ struct i387_struct i387; };
The structure of the process in the kernel is represented as:
3: The process of creating
1: Process scheduling initialization sched_init();
2: Switch to user mode move_to_user_mode();
3: Process initialization
- Manually create process 0 as the parent process of all processes fork() init()
- In process 0, open the standard input, output and error console.
- And create process 1. In process 1, open / etc / RC (configuration file, boot and run configuration, such as logo) and execute / bin/sh
- Process 0 will not end and will be called when no other process calls. Execute for (;) when called pause();
4: Process creates fork() function to create other processes
- Find an empty space in the task linked list to store the current process
- Create a task_struct
- Set task_struct
The creation of a process is a system call, so the starting position is system_call.s, the essence is to copy the current process (assignment of structure, assignment of current structure to new structure)
Secondly, the stack and heap are copied
3.1: procedure corresponding to main function of linux
void main(void) /* This really IS void, no error here. */ { /* The startup routine assumes (well, ...) this */ /* * Interrupts are still disabled. Do necessary setups, then * enable them */ //Memory Copy ROOT_DEV = ORIG_ROOT_DEV; drive_info = DRIVE_INFO; memory_end = (1<<20) + (EXT_MEM_K<<10); memory_end &= 0xfffff000; if (memory_end > 16*1024*1024) memory_end = 16*1024*1024; if (memory_end > 12*1024*1024) buffer_memory_end = 4*1024*1024; else if (memory_end > 6*1024*1024) buffer_memory_end = 2*1024*1024; else buffer_memory_end = 1*1024*1024; main_memory_start = buffer_memory_end; #ifdef RAMDISK main_memory_start += rd_init(main_memory_start, RAMDISK*1024); #endif mem_init(main_memory_start,memory_end); trap_init(); //Set exception vector blk_dev_init();//Block device initialization chr_dev_init();//Character device initialization tty_init();//Console initialization time_init();//time sched_init();//Scheduling initialization buffer_init(buffer_memory_end);//Buffer initialization hd_init();//Hard disk initialization floppy_init();//diskette initialize sti(); move_to_user_mode();//Switch the kernel to user mode. Kernel mode: no preemption, user mode: preemption if (!fork()) { /* we count on this going ok Create process No. 0. If the creation is successful, a 0 will be returned*/ init(); } /* * NOTE!! For any other task 'pause()' would mean we have to get a * signal to awaken, but task0 is the sole exception (see 'schedule()') * as task 0 gets activated at every idle moment (when no other tasks * can run). For task0 'pause()' just means we go check if some other * task can run, and if not we return here. */ for(;;) pause(); }
3.2: process scheduling initialization sched_init()
void sched_init(void) { int i; struct desc_struct * p; if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));//Put init_ The address of the TSS segment of the task (the first process) is placed in position 4 of tss0 in dg set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//Put init_ The address of the LDT segment of the task is placed at position 5 of ldt0 in dg p = gdt+2+FIRST_TSS_ENTRY; //p points to the address of the next tss, position 6, then a and b in p represent tss1 and ldt1 for(i=1;i<NR_TASKS;i++) { //Traverse 63 processes (except the first one) for emptying task[i] = NULL; //Process linked list empty p->a=p->b=0; //ldt and tss in the corresponding GDT are set to 0 p++;//Set ldt and tss in the next GDT to 0 p->a=p->b=0;//Set to 0 p++;//Point to the ldt and tss in the next GDT. Why are they clear twice at a time? } /* Clear NT, so that we won't have troubles with that later on */ //Set some registers __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt);//Set system door outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call);//Set system interrupt - > system call (all actions related to the process are system calls) }
Global Descriptor Table (GDT)
The two macros in TSS and LDT are set in the above code: they represent the location of the first TSS and the first LDT. The following comments represent the location of each descriptor of gdt
/* * Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall * 4-TSS0, 5-LDT0, 6-TSS1 etc ... */ #define FIRST_TSS_ENTRY 4 #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
The relationship between GDT and a single process is shown in the figure below
3.3 init() function
When the linux initialization file is completed, init will be called to create the first process
void init(void) { int pid,i; setup((void *) &drive_info);//Set drive information (void) open("/dev/tty0",O_RDWR,0); //Open the standard input console with handle 0 (void) dup(0);//Open the standard output console, where dup (int fildes) is used to copy file descriptors and redirect input and output (void) dup(0);//Open the standard error console printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS, NR_BUFFERS*BLOCK_SIZE); printf("Free mem: %d bytes\n\r",memory_end-main_memory_start); if (!(pid=fork())) {//Create process 1, return 0 successfully, open rc and run sh in the new process close(0); if (open("/etc/rc",O_RDONLY,0))//configuration information _exit(1); execve("/bin/sh",argv_rc,envp_rc);//Execute shell commands _exit(2); } if (pid>0)//In parent process while (pid != wait(&i))//Wait for the parent process to exit /* nothing */; while (1) { //When the above creation process fails if ((pid=fork())<0) { //Create the process again. In case of failure printf("Fork failed in init\r\n"); continue; } if (!pid) {//pid = 0 successful close(0);close(1);close(2); //Close input, output, error handle setsid(); (void) open("/dev/tty0",O_RDWR,0);//Open input, output, error handle (void) dup(0); (void) dup(0); _exit(execve("/bin/sh",argv,envp));//Open sh again, and the parameter argv is different from the above } while (1) if (pid == wait(&i)) break; printf("\n\rchild %d died with code %04x\n\r",pid,i); sync(); } _exit(0); /* NOTE! _exit, not exit() */ }
3.4 fork function
3.4. 1: System fork_ sys_ fork:
The process creation belongs to the system call, so the starting position is system_call.s medium
//system_call.s .align 2 _sys_fork: call _find_empty_process //Find a free location in the process list testl %eax,%eax js 1f push %gs pushl %esi pushl %edi pushl %ebp pushl %eax call _copy_process addl $20,%esp 1: ret
3.4.2: find_empty_process
Find an idle location in the process linked list, return the location information i, and assign a process number to the currently created process
int find_empty_process(void) { int i; repeat: if ((++last_pid)<0) last_pid=1; for(i=0 ; i<NR_TASKS ; i++) if (task[i] && task[i]->pid == last_pid) goto repeat; for(i=1 ; i<NR_TASKS ; i++) if (!task[i]) return i; return -EAGAIN; }
3.4.3: copy_process:
Create a progressive structure and put it in the linked list
/* * Ok, this is the main fork-routine. It copies the system process * information (task[nr]) and sets up the necessary registers. It * also copies the data segment in it's entirety. */ int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, long ebx,long ecx,long edx, long fs,long es,long ds, long eip,long cs,long eflags,long esp,long ss)//nr: is the process number and the value of other general registers { struct task_struct *p; //The body of the process int i; struct file *f; p = (struct task_struct *) get_free_page();//Allocate space if (!p) return -EAGAIN; task[nr] = p;//Put the process structure into the process linked list *p = *current; /* NOTE! this doesn't copy the supervisor stack */ p->state = TASK_UNINTERRUPTIBLE;//Set status, non interruptible status, to prevent immediate execution p->pid = last_pid; p->father = current->pid; p->counter = p->priority; p->signal = 0; p->alarm = 0; p->leader = 0; /* process leadership doesn't inherit */ p->utime = p->stime = 0; p->cutime = p->cstime = 0; p->start_time = jiffies; p->tss.back_link = 0; p->tss.esp0 = PAGE_SIZE + (long) p; p->tss.ss0 = 0x10; p->tss.eip = eip; p->tss.eflags = eflags; p->tss.eax = 0; p->tss.ecx = ecx; p->tss.edx = edx; p->tss.ebx = ebx; p->tss.esp = esp; p->tss.ebp = ebp; p->tss.esi = esi; p->tss.edi = edi; p->tss.es = es & 0xffff; p->tss.cs = cs & 0xffff; p->tss.ss = ss & 0xffff; p->tss.ds = ds & 0xffff; p->tss.fs = fs & 0xffff; p->tss.gs = gs & 0xffff; p->tss.ldt = _LDT(nr); p->tss.trace_bitmap = 0x80000000; if (last_task_used_math == current) //The current process uses a coprocessor to create the current coprocessor __asm__("clts ; fnsave %0"::"m" (p->tss.i387)); if (copy_mem(nr,p)) { //Copy the code segment and data segment, and 0 is returned successfully task[nr] = NULL; free_page((long) p); return -EAGAIN; } for (i=0; i<NR_OPEN;i++)//If the parent process has an open file, it will open the file if there is no child process if (f=p->filp[i]) f->f_count++;//File open count plus 1 if (current->pwd) //The parent process has the current path current->pwd->i_count++;//Also add 1 if (current->root) current->root->i_count++; if (current->executable) current->executable->i_count++; /*Find the location where the current process descriptor in gdt should be stored and the addresses of tss and ldt through nr*/ set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); p->state = TASK_RUNNING; /* do this last, just in case The status is set to operable and ready for operation*/ return last_pid; //Returns the ID number of the process }
3.4.4: copy_mem()
Copy code and data segments
int copy_mem(int nr,struct task_struct * p) { unsigned long old_data_base,new_data_base,data_limit; unsigned long old_code_base,new_code_base,code_limit; code_limit=get_limit(0x0f); //Size of code snippet data_limit=get_limit(0x17); //Size of data segment old_code_base = get_base(current->ldt[1]); //Address of old code segment old_data_base = get_base(current->ldt[2]); //Address of old data segment if (old_data_base != old_code_base) panic("We don't support separate I&D"); if (data_limit < code_limit) panic("Bad data_limit"); new_data_base = new_code_base = nr * 0x4000000;//Get the address of the new code segment and data segment nr*64M, and each process has a size of 64M p->start_code = new_code_base; set_base(p->ldt[1],new_code_base);//Set new snippet address to ldt[1] set_base(p->ldt[2],new_data_base);//Set the new data segment address to ldt[2] if (copy_page_tables(old_data_base,new_data_base,data_limit)) { //copy old data segment to new data segment free_page_tables(new_data_base,data_limit); //Release on failure return -ENOMEM; } return 0; }