Linux_fork与exec

 fork()与 exec()

fork()函数原型:pid_t fork(void)
                                        typedef  int  _kernel_pid_t  
                                        
typedef  _kernel_pid_t  pid_t

fork()会新生成一个进程,调用fork()的进程为父进程,新生成的进程为子进程,fork()调用一次返回两次,在父进程中返回返回子进程的pid,在子进程中返回0,失败返回-1

父子进程共享数据内容栈区数据、全局数据、堆区数据、文件描述符、
特点:
父子进程共享数据段
fork之前打开的文件描述符,父子进程共享fork之前打开文件的读写偏移量
fork之后,父子进程相互独立单独执行

fork之后子进程会拷贝父进程的PCB结构,然后对PCB里面的数据做修改,父进程的页表直接拷贝给子进程,父子进程共享所有的数据空间。
fork()在生成子进程时,用到了写时拷贝技术(作用),不执行父进程数据段、栈、堆的完全复制,这些区域由父子进程共享,内核将他们的访问权限改为只读,如果父子进程中任何一个试图修改这些区域,则内核只为修改区域的内存制作一个副本,并且是以虚拟存储器中的一页为单位复制

fork():创建子进程的过程
Linux通过clone()系统调用实现fork(),随后clone()调用do_fork()
do_fork()完成创建了创建中的大部分工作,该函数调用copy_process(),然后程序开始运行。
  copy_process()流程:
 1、调用dup_task_struct()为子进程创建一个内核栈,thread_info结构体和tack_struct结构体,这些值与当前进程相同,即此时,子进程和父进程的描述符完全相同、
 2、检查并确保子进程的创建,并检查当前用户所拥有的进程数目没有超出它给分配的资源限制
 3、子进程使自己和父进程区分开来,进程描述符内的许多成员都要被清零或初始化,进程描述符成 员并不是继承而来,主要统计信息继承而来,进程描述符中的大多数据都是共享的
 4、设置子进程状态(TASK_NUINTERRUPTIBLE)以保证其不会投入运行
 5、调用copy_flags()更新task_struct的flags成员(超级用户权限标志PF_SUPERPRIV被清零,设置进程还没有调用exec()的函数标PF_FORKNOEXEC)
 6、调用alloc_pid()为相信进程分配一个有效的PID
 7、根据传递的clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号、处理函数、进程地址空间和命名空间(一般情况下这些资源会被给定的所有线程共享,否则这些资源对每个进程是不同的,拷贝到这里)
 8、返回指向子进程的指针
 9、返回到do_fork(),如果copy_process()返回成功新创建的子进程被唤醒并投入运行
 10、内核有意使子进程先执行,一般会立马调用exec(),避免写时拷贝的额外开销,如果父进程先执行,可能会想地址空间写入数据

 

exec()
调用exec()并没有产生新的进程,一个进程一旦调用exec(),它本身就死亡,系统会把代码段替换成新的程序的代码,唯一保留了原进程的id,对于系统而言还是同一个进程只是执行了其它的程序。只有fork()和vfork()才能创建进程。在使用exec()前,首先要使用fork(),创建一个子进程,子进程调用exec()函数

exec()函数族中的函数
execl(const char *path,const char *arg...);
execv(const char *path,char *const argv[]);
execle(const char *path,const char *arg,...,char *const envp[]);
execve(const char *path,const char*argv[],char *const envp[]);
path:程序的路径,argc:参数,envp:环境变量
execlp(const char *file,const char *arg,....);
execvp(const char *file,char *const argv[]);
前四个取路径做参数,后两个取文件名做参数
l:list          v:vector
excel、excele、excelp:要求新程序的每一个命令行参数都说明为一个单独的参数,参数表以空指针结尾
execv、execve、execvp:现构建一个指向各个参数的指针数组,将该数组的地址作为这三个函数的地址

僵死进程:父进程未结束,子进程结束,并且父进程未获取子进程的退出状态,即进程主体空间已释放,只有PCB未释放
处理方法:
1父进程中调用wait或waitpid获取子进程的退出状态,这种方式可能导致父进程在wait或waitpid调用出现阻塞运行,直到子进程退出。
2、父进程调用signal(SIGHLD,SIG_IGN),来忽略SIGHLD信号,这样子进程结束后会由内核释放资源
3、对子进程的退出捕获它们的退出信号SIGHLD,父进程退出信号时,在信号处理函数中调用wait或waitpid操作来释放它们的资源。

#include<sys/wait.h>
pid_t wait(int *attloc);
pid_t waitpid(pid_t pid,int *statloc,int options);成功返回pid 0,失败返回-1

孤儿进程:父进程结束,子进程未结束,孤儿进程会被系统守护进程init收养,并为它们完成状态收集工作。
守护进程:守护进程、精灵进程,在系统启动时自启动,在系统关闭时终止,生存期比较长,后台运行,ps -axj查看守护进程,init进程负责各运行层次间的系统服务。
守护进程编程规则:
1、调用umask(mode_t umsk())函数将文件模式创建屏蔽字设置为0
2、调用fork(),然后使父进程退出(exit),父进程退出,使shell可以继续允许,子进程继承父进程的进程组,子进程就不会是一个组长进程,就可以调用setsid创建新的会话
3、调用setsid()创建一个新会话,子进程成为会话的首进程,子进程成为一个新进程组的组长进程,没有控制终端
4、调用fork(),然后使父进程退出(exit),使子进程不是会话组长,从而禁止进程重新打开控制终端
4、将当前目录更改为根目录
5、关闭不再需要的文件描述符
6、某些守护进程打开/dev/null使其具有文件描述符0标准输入,1标准输出,2标准错误,这样任何一个进程就不会产生其它不好的效果

如何调试跟踪子进程
使用gdb调试的时候,gdb只能跟踪一个进程。
1、在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程(默认跟踪父进程)。
set follow-fork-mode child 设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置gdb在fork之后跟踪父进程。

2、在fork函数处设置断点,run、set follow-fork-mode child选择跟踪子进程。
进入gdb调试第一步,输入list查看代码,设置断点。
gdb跟踪子进程,采用n单步执行时,父进程不会单步执行,直接正常运行(run)完,直接输出结果到屏幕上,而子进程会受到调试控制,一步一步的执行。在创建子进程之前,设置好跟踪哪一个进程即可,没有跟踪的进程会正常执行,输出结果,不受调试控制。
当有多个子进程时,需要选择跟踪其中一个子进程,则需要根据循环因子i来控制,i可以判别是哪一个进程,进而来设置条件断点(b 17 if i==3),set follow-fork-mode child  跟踪第3个子进程,因为没设置断点,其余子进程直接运行输出结果。

strace:    监控当前进程,跟踪进程执行时的系统调用和接收信号
ltrace:    跟踪进程函数调用库的情况

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值