shell编程实践1

OS实验发了源码,很简易,需要我们寻找里面的坑并修改。跟舍友通力合作,大概了解了其中一些原理。

  • 内部外部命令。内部命令存在于shell源码中,基本不会创建新进程。外部命令包括位于sbin/bin中的一类(如ps)和位于目录中的可执行文件(./a.out)。
  • 作业控制,前后台切换。主要涉及到三个命令 fg bg ctrl_z。这里面进程间的通信至关重要,主要利用kill(发送信号),signal(绑定信号),waitpid(暂停当前进程等待信号)实现。源码中的坑也主要在这里。
  • 第一个涉及到signal的绑定时间问题,实现后台命令时,存在父进程和子进程的同步关系,子进程需要确保父进程成功增加作业后开始执行,故设计了一个利用goon==0参数的死循环,把SIGUSR1信号与setgoon绑定(功能是把goon置1),接到父进程发送的SIGUSR1信号后,goon置1,跳出死循环,开始执行后台命令,在确保输出执行信息后,向父进程发送SIGUSR1信号,父进程结束死循环,继续下面的输出(输出shell信息),问题在于signal的绑定时间,父进程和子进程各需kill发一个信号,signal绑定一个信号,而在没有绑定特定信号处理函数的情况下,进程接到该信号后会执行默认处理,对应的是结束进程,也就是说要保证父进程发给子进程之前,子进程已经通过signal绑定好,子进程发给父进程之前,父进程也绑定好,有人通过在父进程里加sleep(1)解决这个问题,个人认为是不太合适的,因为这样具有随机性,在时间片分配不定的情况下,很有可能出现意外情况。我的处理是在父进程fork出子进程之前调用signal()把SIGUSR1和setGoon()绑定,通过测试验证,子进程在fork()时也会把这一绑定复制过去,这样就勉强解决了这个问题。
  • 第二个坑主要在waitpid()函数上,waitpid的第三个参数对于等待什么信号作出了限制,如果直接传0,默认是等待子进程的结束信号,也就是说子进程结束,waitpid函数才会返回,父进程才能继续,而当我们按下ctrlZ时,子进程挂起,同样需要父进程继续运行,所以把第三个参数改为 WUNTRACED,这样在waitpid在得到SIGINT信号时,也会返回,达到shell继续工作。(注:waitpid意外退出会返回-1,如不存在第一个参数pid对应的子进程等情况)
  • 第三个坑是后台执行的程序也会被执行,这里是因为源码的做法导致父进程(shell)和子进程(后台跑得程序)属于同一个进程组,且父进程是这个进程组的组长,所以我们按CtrlZ发给shell的信号,后台执行的程序也能收到,导致同样会挂起暂停执行。勉强的解决方法是每创建一个子进程都给其分配一个独立的组号。利用setpgid()或者setpgrp()函数。前者的参数较为复杂,注意区分。
  • 除了这三个坑,还对一些bug进行了初步修改。
  • 一个是删除任务的问题,源码中是通过把SIGCHILD和rmjob()绑定实现删除任务,此外还设置了一个全局变量ignore,设置这个参数主要是考虑到SIGCHILD的特性,子程序在挂起,结束,挂起后继续执行三种情况下都会执行,所以源码采取了如下做法:在执行ctrlZ,fg,bg操作时,将ignore置1,在rmjob()中进行判断,ignore==1便直接退出,否则才进行删除任务操作。这样保证了子进程挂起和挂起后继续执行时不会被删除(其实利用sigation()函数的siginfo_t应该会有更合理方便的处理方式,此处没有深入研究)。但是这样做会出现一个问题,那就是子程序在后台运行,前台处于IDLE状态,在shell中直接用fg将子程序切换到前台的话,就会使ignore置1,而这时子程序结束时发送信号触发rmjob后,ignore仍处于1状态,故任务没有成功删除。为了解决这个问题,我将job的结构体加了一个参数 int isback,用于记录任务是否处于后台,在加上job原本的参数state(RUNNING/STOPPED),去除了这个bug。方法是在fg的函数中,判断要切换的任务的状态,若 isBack==1 && state==running,则把ignore置0,保证这种情况下任务也能成功删除。
  • 另一个是僵尸进程的问题。要保证进程结束后被清除,就需要在父进程中对其结束信号SIGCHILD进行waitpid/wait处理。既然涉及到删除任务/进程,那么理应在rmjob中,而我在rmjob中加了一个waitpid之后,却有一种情况下会进入死循环:./Demo  -  crtlZ  -  fg,经过一番漫长的调试,发现rmjob中的waitpid返回异常,perror看错误信息是没有对应的子进程,分析程序中所有的waitpid调用发现,fg函数中为了等待前台程序执行完毕再继续父进程,加了一个waitpid,而这个waitpid实际上已经起到了给那个结束的子进程成收尸的作用,于是rmjob中的waitpid再想收尸就找不到孩子了!找到问题所在,改就好办了。在rmjob中的waitpid之前加一个判断,后台运行的程序才在rmjob里收尸,因为其他情况都已经在其他部分处理了:用fg切换到前台的程序在fg函数里收,直接执行的非后台外部命令在命令执行函数里收,这样就分三处解决了子程序结束收尸的问题。

用printf调试分析也是不容易啊,先说这些,接下来要搞yacc lex和管道以及通配符了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值