linux init进程创建

目录

0号进程的创建

1、进程的PCB task_struct

2、0号进程静态创建

3、定义内核栈task_union

4、如何把0号进程内核栈,放在.data.init_data段里?

进程创建过程

1、设置子进程的上下文信息

 2、wake_up_process


扫描二维码关注公众号,回复: 14650847 查看本文章

0号进程的创建

0号进程为init进程,执行顺序:系统上电->系统固件加载->汇编入口->kernel_main

1、进程的PCB task_struct

struct task_struct {
	enum task_state state;
	enum task_flags flags;
	int pid;
	struct cpu_context cpu_context;
	struct list_head run_list;
	int counter;
	int priority;
	int need_resched;
	int preempt_count;
	struct task_struct *next_task;
	struct task_struct *prev_task;
};

2、0号进程静态创建

#define INIT_TASK(task) \
{                      \
	.state = 0,     \
	.priority = 20,   \
	.counter = DEF_COUNTER, \
	.flags = PF_KTHREAD,   \
	.pid = 0,     \
	.preempt_count = 0, \
	.need_resched = 0, \
	.next_task = (struct task_struct *)&task, \
	.prev_task = (struct task_struct *)&task, \
}

 需要为0号进程分配栈空间。通常做法把0号进程的内核栈空间直接链接到数据段里。其他进程的内核栈是动态分配的。

3、定义内核栈task_union

union task_union {
	struct task_struct task;
	unsigned long stack[THREAD_SIZE/sizeof(long)];
};

task_union数据结构存储在内核栈的底部。

4、如何把0号进程内核栈,放在.data.init_data段里?

通过GCC的__attribute__属性 把task_union编译连接到.data.init_task段中

#define __init_task_data __attribute__((__section__(".data.init_task")))

0号进程为init进程  

union task_union init_task_union __init_task_data = {INIT_TASK(init_task_union.task)};

链接文件 增加.data.init_task

SECTIONS
{
...
	_erodata = .;
	_data = .;
	.data : {
		*(.data)
		. = ALIGN(PAGE_SIZE);
		*(.data.init_task)
	}
...
}


进程创建过程

int do_fork(unsigned long clone_flags, unsigned long fn, unsigned long arg)
{
        struct task_struct *p;
        int pid;

        p = (struct task_struct *)get_free_page();
        if (!p)
                goto error;

        memset(p, 0, sizeof(*p));

        pid = find_empty_task();
        if (pid < 0)
                goto error;

        if (copy_thread(clone_flags, p, fn, arg))
                goto error;

        p->state = TASK_RUNNING;
        p->pid = pid;
        p->counter = (current->counter + 1) >> 1;
        current->counter >>= 1;
        p->need_resched = 0;
        p->preempt_count = 0;
        p->priority = 2;
        total_forks++;
        g_task[pid] = p;
        SET_LINKS(p);
        wake_up_process(p);

        return pid;

error:
        return -1;
}
  • 获取task_struct进程的内存,分配4KB页面存储内核栈
  • 获得pid号
  • copy进程信息
  • 设置进程参数,状态,优先级,时间片
  • 将进程加入到就绪队列中wake_up_process

1、设置子进程的上下文信息

static int copy_thread(unsigned long clone_flags, struct task_struct *p,
                unsigned long fn, unsigned long arg)
{
        struct pt_regs *childregs;

        childregs = task_pt_regs(p);
        memset(childregs, 0, sizeof(struct pt_regs));
        memset(&p->cpu_context, 0, sizeof(struct cpu_context));

        if (clone_flags & PF_KTHREAD) {
                childregs->pstate = PSR_MODE_EL1h;
                p->cpu_context.x19 = fn;
                p->cpu_context.x20 = arg;
        }

        p->cpu_context.pc = (unsigned long)ret_from_fork;
        p->cpu_context.sp = (unsigned long)childregs;

        return 0;
}

关键点

  • x19:回调函数
  • x20:回调函数的参数
  • pc:指针指向ret_from_fork,SP寄存器指向内核的pt_regs栈框

 ret_from_fork的汇编实现,此函数是新进程执行的开始,x19保存了进程的回调函数,x20保存了回调函数的参数。

.align 2
.global ret_from_fork
ret_from_fork:

        bl schedule_tail
        cbz x19, 1f
        mov x0, x20
        blr x19
1:
        b ret_to_user
  • 如果next进程是内核线程,在创建时会使X19寄存器指向task_start
  • 如果X19寄存器的值为0,说明这个next进程是用户进程,跳转到ret_to_user
  • 如果next进程是内核线程,那么跳转到内核线程的回调函数里。 

 2、wake_up_process

将进程加入到就绪队列中,等待调度

void wake_up_process(struct task_struct *p)
{
        struct run_queue *rq = &g_rq;
        p->state = TASK_RUNNING;
        enqueue_task(rq, p);
}

 参考

https://course.0voice.com/v1/course/intro?courseId=2&agentId=0

猜你喜欢

转载自blog.csdn.net/WANGYONGZIXUE/article/details/128606106