zoukankan      html  css  js  c++  java
  • 2018-2019-1 20189229 《Linux内核原理与分析》第七周作业

    进程的描述和进程的创建

    操作系统内核实现的操作系统三大管理功能:
    - 进程管理
    - 内存管理
    - 文件系统

    进程描述符

    linux中用一个数据结构struct task_struct来描述进程,称为进程描述符,具体结构示意图如下:

    其中
    - state 运行状态
    - stack 进程堆栈
    - struct list_head tasks 进程链表
    - 把所有进程都用双向链表链起来,如下图:

    操作系统原理进程状态

    操作系统原理进程状态主要有:‘就绪态’,‘运行态’,‘阻塞态’三种。具体状态转换图如下:

    0号进程的初始化

    init_task为第一个进程(0号进程)的进程描述符结构体变量,其初始化是通过硬编码方式固定下来的。除此之外,其他所有进程的初始化都是通过do_fork复制父进程的方式初始化的。INIT_TASK宏定义摘录如下:

    #define INIT_TASK(tsk)	
    174{									
    175	.state		= 0,						
    176	.stack		= &init_thread_info,				
    177	.usage		= ATOMIC_INIT(2),				
    178	.flags		= PF_KTHREAD,					
    179	.prio		= MAX_PRIO-20,					
    180	.static_prio	= MAX_PRIO-20,					
    181	.normal_prio	= MAX_PRIO-20,					
    182	.policy		= SCHED_NORMAL,					
    183	.cpus_allowed	= CPU_MASK_ALL,					
    184	.nr_cpus_allowed= NR_CPUS,					
    185	.mm		= NULL,						
    186	.active_mm	= &init_mm,					
    187	.se		= {						
    188		.group_node 	= LIST_HEAD_INIT(tsk.se.group_node),	
    189	},								
    190	.rt		= {						
    191		.run_list	= LIST_HEAD_INIT(tsk.rt.run_list),	
    192		.time_slice	= RR_TIMESLICE,				
    193	},								
    194	.tasks		= LIST_HEAD_INIT(tsk.tasks),			
    195	INIT_PUSHABLE_TASKS(tsk)					
    196	INIT_CGROUP_SCHED(tsk)						
    197	.ptraced	= LIST_HEAD_INIT(tsk.ptraced),			
    198	.ptrace_entry	= LIST_HEAD_INIT(tsk.ptrace_entry),		
    199	.real_parent	= &tsk,						
    200	.parent		= &tsk,						
    201	.children	= LIST_HEAD_INIT(tsk.children),			
    202	.sibling	= LIST_HEAD_INIT(tsk.sibling),			
    203	.group_leader	= &tsk,						
    204	RCU_POINTER_INITIALIZER(real_cred, &init_cred),			
    205	RCU_POINTER_INITIALIZER(cred, &init_cred),			
    206	.comm		= INIT_TASK_COMM,				
    207	.thread		= INIT_THREAD,					
    208	.fs		= &init_fs,					
    209	.files		= &init_files,					
    210	.signal		= &init_signals,				
    211	.sighand	= &init_sighand,				
    212	.nsproxy	= &init_nsproxy,				
    213	.pending	= {						
    214		.list = LIST_HEAD_INIT(tsk.pending.list),		
    215		.signal = {{0}}},					
    216	.blocked	= {{0}},					
    217	.alloc_lock	= __SPIN_LOCK_UNLOCKED(tsk.alloc_lock),		
    218	.journal_info	= NULL,						
    219	.cpu_timers	= INIT_CPU_TIMERS(tsk.cpu_timers),		
    220	.pi_lock	= __RAW_SPIN_LOCK_UNLOCKED(tsk.pi_lock),	
    221	.timer_slack_ns = 50000, /* 50 usec default slack */		
    222	.pids = {							
    223		[PIDTYPE_PID]  = INIT_PID_LINK(PIDTYPE_PID),		
    224		[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID),		
    225		[PIDTYPE_SID]  = INIT_PID_LINK(PIDTYPE_SID),		
    226	},								
    227	.thread_group	= LIST_HEAD_INIT(tsk.thread_group),		
    228	.thread_node	= LIST_HEAD_INIT(init_signals.thread_head),	
    229	INIT_IDS							
    230	INIT_PERF_EVENTS(tsk)						
    231	INIT_TRACE_IRQFLAGS						
    232	INIT_LOCKDEP							
    233	INIT_FTRACE_GRAPH						
    234	INIT_TRACE_RECURSION						
    235	INIT_TASK_RCU_PREEMPT(tsk)					
    236	INIT_TASK_RCU_TASKS(tsk)					
    237	INIT_CPUSET_SEQ(tsk)						
    238	INIT_RT_MUTEXES(tsk)						
    239	INIT_VTIME(tsk)							
    240}
    

    进程之间父子兄弟关系

    进程的描述符数据结构中记录了当前进程的父进程real_parent,记录当前进程的子进程的是双向链表struct list_head children ,记录当前进程的兄弟进程的是双向链表struct list_head sibling。如下图为父子、兄弟关系示意图:

    图中,P0有P1P2P3三个儿子,P1有两个兄弟,P3还有一个儿子,彼此之间用指针或双向链表相连。

    进程的创建过程分析

    fork系统调用具体过程:

    举例演示怎样在用户态下创建一个子进程。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main()
    {
            int pid;
            pid = fork();
            if(pid < 0)
            {
                    fprintf(stderr,"Fork Failed!");
                    exit(-1);
            }
            else if(pid = 0)
            {
                    printf("This is Child Process!
    ");
            }
            else
            {
                    printf("This is Parent Process!
    ");
                    wait(NULL);
                    printf("Child Complete!
    ");
            }
    }
    
    

    运行截图如下:

    此时会出现一个疑问,上述代码中,else if 与else都被执行,也就是fork的返回值竟然有两个,实际上fork系统调用把当前进程又复制了一个子进程,只是fork系统调用在父进程和子进程中的返回值不同。

    fork、vfork、clone创建新进程

    以下代码为fork、vfork、clone三个系统调用内核处理函数。

    /*
    1694 * Create a kernel thread.
    1695 */
    1696pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    1697{
    1698	return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
    1699		(unsigned long)arg, NULL, NULL);
    1700}
    1701
    1702#ifdef __ARCH_WANT_SYS_FORK
    1703SYSCALL_DEFINE0(fork)
    1704{
    1705#ifdef CONFIG_MMU
    1706	return do_fork(SIGCHLD, 0, 0, NULL, NULL);
    1707#else
    1708	/* can not support in nommu mode */
    1709	return -EINVAL;
    1710#endif
    1711}
    1712#endif
    1713
    1714#ifdef __ARCH_WANT_SYS_VFORK
    1715SYSCALL_DEFINE0(vfork)
    1716{
    1717	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
    1718			0, NULL, NULL);
    1719}
    1720#endif
    1721
    1722#ifdef __ARCH_WANT_SYS_CLONE
    1723#ifdef CONFIG_CLONE_BACKWARDS
    1724SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
    1725		 int __user *, parent_tidptr,
    1726		 int, tls_val,
    1727		 int __user *, child_tidptr)
    1728#elif defined(CONFIG_CLONE_BACKWARDS2)
    1729SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
    1730		 int __user *, parent_tidptr,
    1731		 int __user *, child_tidptr,
    1732		 int, tls_val)
    1733#elif defined(CONFIG_CLONE_BACKWARDS3)
    1734SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
    1735		int, stack_size,
    1736		int __user *, parent_tidptr,
    1737		int __user *, child_tidptr,
    1738		int, tls_val)
    1739#else
    1740SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
    1741		 int __user *, parent_tidptr,
    1742		 int __user *, child_tidptr,
    1743		 int, tls_val)
    1744#endif
    1745{
    1746	return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
    1747}
    1748#endif
    

    通过上述代码可看出,三个系统调用都可以创建一个新进程,而且都是通过do_fork函数来创建进程的,只不过传递参数不同。

    do_fork函数

    源码:

    /*
    1618 *  Ok, this is the main fork-routine.
    1619 *
    1620 * It copies the process, and if successful kick-starts
    1621 * it and waits for it to finish using the VM if required.
    1622 */
    1623long do_fork(unsigned long clone_flags,
    1624	      unsigned long stack_start,
    1625	      unsigned long stack_size,
    1626	      int __user *parent_tidptr,
    1627	      int __user *child_tidptr)
    1628{
    1629	struct task_struct *p;
    1630	int trace = 0;
    1631	long nr;
    1632
    1633	/*
    1634	 * Determine whether and which event to report to ptracer.  When
    1635	 * called from kernel_thread or CLONE_UNTRACED is explicitly
    1636	 * requested, no event is reported; otherwise, report if the event
    1637	 * for the type of forking is enabled.
    1638	 */
    1639	if (!(clone_flags & CLONE_UNTRACED)) {
    1640		if (clone_flags & CLONE_VFORK)
    1641			trace = PTRACE_EVENT_VFORK;
    1642		else if ((clone_flags & CSIGNAL) != SIGCHLD)
    1643			trace = PTRACE_EVENT_CLONE;
    1644		else
    1645			trace = PTRACE_EVENT_FORK;
    1646
    1647		if (likely(!ptrace_event_enabled(current, trace)))
    1648			trace = 0;
    1649	}
    1650
    1651	p = copy_process(clone_flags, stack_start, stack_size,
    1652			 child_tidptr, NULL, trace);
    1653	/*
    1654	 * Do this prior waking up the new thread - the thread pointer
    1655	 * might get invalid after that point, if the thread exits quickly.
    1656	 */
    1657	if (!IS_ERR(p)) {
    1658		struct completion vfork;
    1659		struct pid *pid;
    1660
    1661		trace_sched_process_fork(current, p);
    1662
    1663		pid = get_task_pid(p, PIDTYPE_PID);
    1664		nr = pid_vnr(pid);
    1665
    1666		if (clone_flags & CLONE_PARENT_SETTID)
    1667			put_user(nr, parent_tidptr);
    1668
    1669		if (clone_flags & CLONE_VFORK) {
    1670			p->vfork_done = &vfork;
    1671			init_completion(&vfork);
    1672			get_task_struct(p);
    1673		}
    1674
    1675		wake_up_new_task(p);
    1676
    1677		/* forking complete and child started to run, tell ptracer */
    1678		if (unlikely(trace))
    1679			ptrace_event_pid(trace, pid);
    1680
    1681		if (clone_flags & CLONE_VFORK) {
    1682			if (!wait_for_vfork_done(p, &vfork))
    1683				ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
    1684		}
    1685
    1686		put_pid(pid);
    1687	} else {
    1688		nr = PTR_ERR(p);
    1689	}
    1690	return nr;
    1691}
    

    do_fork函数参数

    - clone_flags:子进程创建相关标志,通过此标志可对父进程的资源进行有选择的复制。
    - stack_start:子进程用户态堆栈地址。
    - regs:指向pt_regs结构体的指针。
    - stack_size:用户堆栈的大小,通常设为零。
    - parent_tidptr和child_tidptr:父进程、子进程用户态下的pid地址。
    

    实验跟踪进程创建过程

    更换menu文件,进入gdb,分别在sys_clone、do_fork、dup_task_struct、copy_process、copy_thread、ret_from_fork处设置断点,跟踪fork进程创建过程,实验截图如下:

  • 相关阅读:
    golang 数据结构 优先队列(堆)
    leetcode刷题笔记5210题 球会落何处
    leetcode刷题笔记5638题 吃苹果的最大数目
    leetcode刷题笔记5637题 判断字符串的两半是否相似
    剑指 Offer 28. 对称的二叉树
    剑指 Offer 27. 二叉树的镜像
    剑指 Offer 26. 树的子结构
    剑指 Offer 25. 合并两个排序的链表
    剑指 Offer 24. 反转链表
    剑指 Offer 22. 链表中倒数第k个节点
  • 原文地址:https://www.cnblogs.com/zisong/p/10017889.html
Copyright © 2011-2022 走看看