实验:从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换

学号:446
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
一、实验目的及原理
理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换。

二、实验平台
linux3.16+vm workstation12 pro+vs code

三、阅读task_struct源码
源码来源:http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235;
Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息,在include/linux/sched.h的1235行我们可以看到该数据结构的原型,详情见以上网址。

1235	struct task_struct {
1236	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
1237	void *stack;		//函数堆栈
1238	atomic_t usage;		//函数用例
1239	unsigned int flags;	/* per process flags, defined below */
1240	unsigned int ptrace;
1241	...
...

通过阅读源码,我们可以发现,为了完成进程各种复杂的工作,PCB的结构十分复杂,大致包括:

  • 进程基本信息
  • 调度信息
  • 文件系统信息
  • 内存信息
  • I/O信息
  • 资源信息
  • 现场控制
  • 环境信息

二、分析fork函数对应的内核处理过程do_fork
函数在/kernel/fork.c中可以发现
根据不同的linux内核版本,可以用find指令寻找

find -name fork.c

先看看函数原型

先介绍一下它执行时使用的参数:
/*clone_flags:与clone()参数flags相同。
stack_start:与clone()参数stack_start相同。
stack_size:未使用,总被设置为0。
parent_tidptr,child_tidptr:与clone系统调用中对应参数ptid和ctid相同。
*/
1623	long 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	}
//调用copy_process,复制一份返回一个进程描述符
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		
//获得pid
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
//检查调用的是否时vfork,若是vfork则用另一套方案
1669		if (clone_flags & CLONE_VFORK) {
1670			p->vfork_done = &vfork;
1671			init_completion(&vfork);
1672			get_task_struct(p);
1673		}
1674
//调用wake_up_new_task,将新任务加入调度队列
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}

四、使用gdb跟踪分析一个fork系统调用内核处理函数do_fork
首先是编写代码并编译,在test.c中编写了forktest()函数,如下:
在这里插入图片描述
编译并运行虚拟机:

cd menu
gcc  linktable.c menu.c test.c -lpthread -o init -m32 -static
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
qemu-system-i386 -kernel '/home/isition/linux-5.0/arch/x86/boot/bzImage'  -initrd /home/isition/linux-5.0/rootfs.img -S -s -append nokaslr

接下来开启另一终端,启用gdb调试:

gdb vmlinux
target remote:1234
 b sys_clone
 b dup_task_struct
 b do_fork
 b copy_process
 b copy_thread
 b ret_from_for

设置情况如下:在这里插入图片描述
设置完成进行c操作观看结果:
在这里插入图片描述
观察实验结果得知:进程的建立经历了:sys_clone->do_fork->copy_process->copy_thread->ret_form_fork的过程。
五、理解编译链接的过程和ELF可执行文件格式
从.c文件到可执行文件的过程如下:
在这里插入图片描述
预处理:主要是做一些代码文本的替换工作。(该替换是一个递归逐层展开的过程。)
编译:把预处理完的文件进行一系列词法分析(lex)、语法分析(yacc)、语义分析及优化后生成汇编代码,这个过程是程序构建的核心部分。
汇编:汇编代码->机器指令。
链接:这里讲的链接,严格说应该叫静态链接。多个目标文件、库->最终的可执行文件(拼合的过程)。

五、编程使用exec*库函数加载一个可执行文件
先编写一个用于输出的hello.c
在这里插入图片描述
将其动态或静态编译为hello.o
在这里插入图片描述
编写调用程序并编译执行:
在这里插入图片描述
使用gdb设置断点并观差:
在这里插入图片描述
六、搜索并使用gdb跟踪分析一个schedule()函数
在opengrok上搜索发现与我们的猜想基本一致:
在这里插入图片描述
设置断点并分析:
在这里插入图片描述
七、总结
通过这次实验,让我更加深入的了解了linux的进程的建立等流程,也更加仔细的了解了我们的代码是怎么变成可执行文件并装入内存的,这次实验让我对linux的文件系统、进程调度等方面的知识得到了大大的强化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值