【进程管理】系统调用execve()

子进程通常是按父进程的原样复制进来的,只改变了少量的thread的信息(如返回地址等),如果它还走父进程的路,是没有多大意义的,所以应该执行一个新的可执行程序;execve()是一个族函数,有一系列的库函数都会调用它,如excel(),execlp()等,而system()是fork(),execve(),wait()的组合;


(1)在do_execve()中,reg.ebx中的内容为相应库函数的第一个参数,这个参数是一个指针,指向某个字符串,首先需要使用getname()将它从用户空间拷贝到内核空间,它是通过分配一个物理页面来拷贝的,并不能使用进程的系统空间(太小了,大约只有7KB);然后执行do_execve(),最后还要使用put_name()将所分配的页面释放掉;

(2)在do_execve()中,先使用open_exec()将给定的可执行文件打开,返回一个file文件指针;假定该文件已经打开,从文件中装入可执行程序,为此内核定义了一个可执行文件时所需的信息linux_binprm,以便运行一个可执行文件所需的信息组织起来;bprm首先将上述file赋值,代表着可执行文件的上下文;sh_brang说明可执行文件的性质,当可执行文件是一个shell脚本时,将其设为1,由于现在还不知道,故将它先设为0,表示是一个二进制文件;接下来就是处理可执行文件的参数和环境变量;然后使用count()对于用户空间的argc和envc进行计算个数;然后使用prepare_binprm()先从可执行文件中读入开头的128字节到binprm的bprm缓冲区,这头128个字节包括执行文件的属性的相关信息;最后将filename,envp,argv拷贝到bprm中去;这样所有的信息都已经搜集到了bprm中;

(3)内核中有一个队列formats,挂在此队列的成员是代表着各种可执行文件格式的代理人,每一个成员认识并处理一种特定格式的可执行文件的运行;在search_binary_handler()中,首先遍历这样的一个formats,如果对上号,那就将目标文件装入并将其投入运行;-ENOEXEC只是表示对不上号,并没与发生错误,还要取下一个来继续试;内层循环结束后,如果失败的原因是-ENOEXEC,只表示队列中的所有成员都不认识该目标文件,这时候如果支持动态安装模块,可通过request_module()试着将相应的模块装入;由于因此还要再试一次,故外层循环是两次;

(4)在search_binary_handler()中,load_aout_binary()用来装入可执行程序,以a.out格式为例;所有以a.out格式可执行文件(二进制代码)的开头都应该是exec数据结构(a.out所特有的,而binprm是大家共有的);在exec数据结构中,info其高16位是一个代表目标CPU类型的代码,对于i386这部分值是100,而低16位就是magic number(和各个不同的可执行文件开头的几个字节不同,它有4中取值,OMAGIC和NMAGIC等),如果magic number不符,或者exec结构中提供的信息与实际不符,那就不能认为这个目标文件是a.out格式,所以返回-ENOEXEC;N_TXTOFF会根据代码的特性取得正文在目标文件中的起始位置;ex.data+ex.bss的总大小不能超过rlim的限制大小;接下来就到了子进程告别父进程继承下来的全部用户空间,不管是通过复制还是通过共享继承下来的,通过flush_old_exec()来进行;

(5)在flush_old_exec()中,首先是进程信号的处理,要建立子进程自己的信号处理机制,在make_private_signals中,首先看是否还是需要共享,若如果以前是指针共享,就需要将父进程的信号处理表action复制下来,这里使用到了lazy compution,如果执行不到execv()这不也就省略了;在exec_mmap()中从父进程继承下来的用户空间就要放弃了,如果current->mm是1,说明此空间 是独占的,也就是从父进程复制过来的,那就要使用exit_mmap()释放掉vm_area_struct,并将页面表项都设置成0,在此前要mm_release(),在flush_cache_mm和flush_tlb_mm()使得高速缓存以及快表使得和内存一致;这样fork()认真复制的所有用户数据结构,现在有释放了,因此在execve(),一般结合vfork()来使用,为避免父子进程都对用户堆栈空间的非法写,父进程要等待子进程运行execve();在mm_release()中,up了(task->p_opptr->vfork_sem(以前指向sem))一下,唤醒以前down的父进程;如果使用的指针共享,那就还没有mm_struct,这时先分配一个mm_alloc()一个,并将添加到mmlist中;并通过activate_mm切换到这个新的用户空间(此时它只是一个空壳,一个页面都没有),然后还要使用mm_release来减少共享计数,当共享计数为0时,还要将其下属的数据结构释放掉(通过mmput里除了使用exit_mmap(),还要使用mmdrop销毁页面表,页面目录,mm_struct),因为没有进程使用这个空间了,其实我们这个情景没有这个过程,mm_user的共享计数不会为0;如果是内核线程,它的mm_struct为0,也就是没有用户空间,但是它的active_mm却不为0,然而当一个不具备用户空间的进程(内核线程)被调度运行时,要求active_mm一定指向某个mm_struct,所以需借一个,此时,将它设置成和之前运行的那个进程的active_mm相同(故而我们后面在进程的调度和切换中,可以看到要递增这个mm_struct),而在调度运行停止时,再将它设置成0;而,我们现在以为这内核线程分配了mm_struct,使其升格为了进程,就不再需要借来的active_mm了,就要mmdrop(active_mm),当mm_struct->mm_count为0时,才真正的删除;

(6)flush_old_exec()中,子子进程已经有了自己的信号处理表,所以还要用release_old_signals()来递减父进程的信号处理表;将task_struct->comm指向bprm->filename;如果当前进程只是一个线程,那么它的task_struct结构通过结构中的thread_group挂入由其父进程为首的线程组队列,但是execve()已经使它有了新的mm_struct,即升格为了进程,所以使用do_thread()从这个线程组分离出来;然后使用flush_signal_handler()来处理子进程的服务程序,使它们指向SIG_DFL(即采用预设的响应方式);然后使用flush_old_files()根据位图close_on_exec来关闭一些文件,并将此位图清全0,除了fd为0,1,2;

(7)在load_aout_binary()中,先初始化mm_struct中的一些结构,为以后分配空间并读入可执行代码的影响做好准备,目标代码映像分为text,data,bss三段,设置mm_struct为这每一个段都设置了start,end的指针,然后使用compute_creds()确定进程在开始执行新代码以后所具有的权限(主要根据bprm来设置的);如果magic number是QMAGIC,表示给文件的代码并不是可执行代码,并非纯代码,只读入ex.a_text+ex.a_data,而对于bss段不读入,只分配空间就好了;如果magic number不是QMAGIC,表示给定代码是纯代码,可重入代码,凡是运行过程中的改变的内容都是在堆栈中,要不然就在动态分配的缓冲区中,所以内核将可执行文件映射到了进程的用户空间(用内存),这样swap()所需的盘上空间省去了,调用mmap()之前无需分配空间,已经包含在mmap()中了;下面来处理bss段和堆栈段,set_brk()来申请和分配堆栈段的空间了,bss段为0;接着还要使用steup_arg_pages()将可执行参数而后环境变量所占的物理页面和用户空间的堆栈区顶部建立的虚存空间;此外还要将mian函数保存的参数,环境变量(envp[],argv[],argc)通过create_aout_tables()复制到用户空间的顶端,其实下面就是堆栈段了,使用宏put_user(),get_user(0来搬运;最后使用start_thread()来设置系统空间堆栈中的各个副本,这些指针将会被恢复到CPU的各个寄存器,所以那时候的堆栈将会指向current->mm->start_stack,而返回地址就是EIP的内容;

(8)a.out是二进制可执行文件的形式,对于字符形式的可执行文件(解释程序),比如shell,过程差不多;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值