进程和程序

学号:476
实验资源:https://github.com/mengning/linuxkernel/

一、实验要求
在这里插入图片描述
二、实验内容
1、task_struct数据结构
在这里插入图片描述
task_struct是进程控制块PCB的数据结构,为了方便管理进程准备,里面包含了内核所需要了解的进程的信息。
下面对整个task_struct所定义的进程控制信息进行分析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2、进程的创建

fork、vfork、clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现的
1)子进程被创建后继承了父进程的资源。
2)子进程共享父进程的虚存空间。
3)写时拷贝 (copy on write):子进程在创建后共享父进程的虚存内存空间,写时拷贝技术允许父子进程能读相同的物理页。只要两者有一个进程试图写一个物理页,内核就把这个页的内容拷贝到一个新的物理 页,并把这个新的物理页分配给正在写的进程
4)子进程在创建后执行的是父进程的程序代码。

对于父进程 fork 返回子进程号,对于子进程 fork 返回 0 ,fork 先是调用 find_empty_process 为子进程找到一个空闲的任务号,然后调用 copy_process 复制进程, fork 返回 copy_process 的返回值 last_pid ,也就是子进程号。所以fork()实际上是一次调用,两次返回。
fork函数创建新进程是通过下面的一系列函数实现的:

fork()-->sys_clone()-->do_fork()-->copy_process()-->dup_task_struct()-->copy_thread()-->ret_from_fork()
  • 用户空间的寄存器、用户态堆栈等信息在切换到内核态的上下文时保存在内核栈中,父进程在内核态(dup_task_struct)复制出子进程,但子进程作为一个独立的进程,之后被调度运行时必须有一个指令地址,进程切换时,ip地址及当前内核栈的位置esp都存在于thread_info中,由copy_thread设置其thread.ip指向ret_from_fork作为子进程执行的第一条语句,并完成了内核态到用户态的切换。
  • 进程创建由系统调用来建立新进程,归根结底都是调用do_fork来实现。do_fork主要就是调用copy_process。
  • copy_process()主要完成进程数据结构,各种资源的初始化。初始化方式可以重新分配,也可以共享父进程资源,主要根据传入clone_flags参数来确定。将task_struct结构体分配给子进程,并为其分配pid,最后将其加入可运行队列中。
  • dup_task_struct()为子进程获取进程描述符
  • copy_thread()函数将父进程内核栈复制到子进程中,同时设置子进程调度后执行的第一条语句地址为do_frok返回,并将保存返回值的寄存器eax值置为0,因此子进程返回为0,而父进程继续执行之后的初始化,最后返回子进程的pid(tgid)
  • 总结:通过调用do_fork来实现进程的创建;
    复制父进程PCB-task_struct来创建一个新进程,给新进程分配一个新的内核堆栈;
    修改复制过来的进程数据,比如pid、进程链表等、执行copy_process和copy_thread;
    成功创建进程

3、使用gdb跟踪分析内核处理函数do_fork()
和之前实验步骤相同启动MenuOS,给创建进程相关的函数打断点观察运行过程:
在这里插入图片描述
接下来追踪每一步:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码追踪结果和上述分析结果相同。

4、理解编译链接的过程和ELF可执行文件格式
1)linux系统中,可执行程序一般要经过预处理、编译、汇编、链接、执行等步骤。
2)c代码,经过预处理,变成汇编代码;经过汇编器,变成目标代码;连接成可执行文件;加载到内核中执行。
3)编译连接流程:execve->do_execve->search_binary_handle->load_binary
4)编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接:
首先编辑一个hello.c

#include <stdio.h>
#include <stdlib.h>  
int main()
{
     printf("Hello World!\n");
     return 0;
}

按照上述2)中步骤生成预处理文件、汇编代码、目标代码和可执行文件:

gcc -E -o hello.cpp hello.c -m32
gcc -x cpp-output -S -o hello.s hello.cpp -m32
gcc -c hello.s -o hello.o -m32
gcc -o hello hello.o -m32
./hello

在这里插入图片描述
也可以通过静态编译,把所有需要执行所依赖的东西放到程序内部

gcc -o hello.static hello.o -m32 -static
./hello.static

在这里插入图片描述
静态链接方式:在程序运行之前完成所有的组装工作,生成一个可执行的目标文件;
动态链接方式:在程序已经为了执行被装载入内存之后完成链接工作,并且在内存中一般只保留该编译单元的一份拷贝。

5、使用gdb跟踪分析内核处理函数do_execve:
在这里插入图片描述
在这里插入图片描述

6、理解linux系统中进程调度的时机(查看schedule()函数)
中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
内核线程直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,内核线程既可以主动调度也可以被动调度;
用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

7、使用gdb跟踪分析一个schedule()函数
在这里插入图片描述
分别在schedule函数、pick_next_task函数、context_switch函数和__switch_to函数处打断点并继续执行观察函数调用的过程
发现context_switch和pick_next_task函数都在__schedule函数中。

8、分析switch_to中的汇编代码,理解上下文的切换机制,以及与中断上下文切换的关系
switch_to实现了进程之间的切换:

  • 首先在当前进程prev的内核栈中保存esi,edi及ebp寄存器的内容。
  • 然后将prev的内核堆栈指针ebp存入prev->thread.esp中。
  • 把将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中
  • 将popl指令所在的地址保存在prev->thread.eip中,这个地址就是prev下一次被调度
  • 通过jmp指令(而不是call指令)转入一个函数__switch_to()
  • 恢复next上次被调离时推进堆栈的内容。从现在开始,next进程就成为当前进程而真正开始执行

汇编代码分析:

asm volatile("pushfl\n\t"      /* 保存当前进程的标志位 */   
         "pushl %%ebp\n\t"        /* 保存当前进程的堆栈基址EBP   */ 
         "movl %%esp,%[prev_sp]\n\t"  /* 保存当前栈顶ESP   */ 
         "movl %[next_sp],%%esp\n\t"  /* 把下一个进程的栈顶放到esp寄存器中,完成了内核堆栈的切换,从此往下压栈都是在next进程的内核堆栈中。   */ 
       

		 "movl $1f,%[prev_ip]\n\t"    /* 保存当前进程的EIP   */ 
         "pushl %[next_ip]\n\t"   /* 把下一个进程的起点EIP压入堆栈   */    
         __switch_canary                   
         "jmp __switch_to\n"  /* 因为是函数所以是jmp,通过寄存器传递参数,寄存器是prev-a,next-d,当函数执行结束ret时因为没有压栈当前eip,所以需要使用之前压栈的eip,就是pop出next_ip。  */ 


		 "1:\t"               /* 认为next进程开始执行。 */         
		 "popl %%ebp\n\t"     /* restore EBP   */    
		 "popfl\n"         /* restore flags */  
                                    
		 /* output parameters 因为处于中断上下文,在内核中
		 prev_sp是内核堆栈栈顶
		 prev_ip是当前进程的eip */                
		 : [prev_sp] "=m" (prev->thread.sp),     
		 [prev_ip] "=m" (prev->thread.ip),  //[prev_ip]是标号        
		 "=a" (last),                 
                                    
		/* clobbered output registers: */     
		 "=b" (ebx), "=c" (ecx), "=d" (edx),      
		 "=S" (esi), "=D" (edi)             
                                       
		 __switch_canary_oparam                
                                    
		 /* input parameters: 
		 next_sp下一个进程的内核堆栈的栈顶
		 next_ip下一个进程执行的起点,一般是$1f,对于新创建的子进程是ret_from_fork*/                
		 : [next_sp]  "m" (next->thread.sp),        
		 [next_ip]  "m" (next->thread.ip),       
                                        
	     /* regparm parameters for __switch_to(): */  
		 [prev]     "a" (prev),              
		 [next]     "d" (next)               
                                    
		 __switch_canary_iparam                
                                    
		 : /* reloaded segment registers */           
		 "memory");                  
} while (0)

9、总结

  • 内核线程既可以实现主动调度也可以实现被动调度,主动调度是直接调用schedule函数进行调度,被动调度是在中断处理过程中进行调度。
  • 而用户态进程无法实现主动调度,只能在中断处理过程中进行调度
  • schedule()函数实现进程调度,context_ switch完成进程上下文切换,switch_ to完成寄存器的切换。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值