Kernel process initialization and creation

Posted by jmantra on Wed, 22 Dec 2021 05:23:39 +0100

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;
}

Topics: Process