1. //第一层系统调用
  2. asmlinkage long sys_exit(int error_code)
  3. {
  4. do_exit((error_code&0xff)<<8);
  5. }

其主体是do_exit,接下来我们来看看do_exit的实现
  1. NORET_TYPE void do_exit(long code)
  2. {
  3. struct task_struct *tsk = current;//获取当前进程描述符
  4. if (in_interrupt())//禁止中断时调用do_exit
  5. panic("Aiee, killing interrupt handler!");
  6. if (!tsk->pid)//空转进程也就是0号进程禁止退出
  7. panic("Attempted to kill the idle task!");
  8. if (tsk->pid == 1)//1号进程禁止退出
  9. panic("Attempted to kill init!");
  10. tsk->flags |= PF_EXITING;//退出进程时,设置此标志位
  11. /*
  12. 进程退出时,可能已经设置了实时定时器,real_timer已挂载到内核定时器队列,
  13. 现在进程要退出,没必要存在了,就把当前进程从定时器队列中脱离出来
  14. */
  15. del_timer_sync(&tsk->real_timer);
  16. fake_volatile:
  17. #ifdef CONFIG_BSD_PROCESS_ACCT
  18. acct_process(code);
  19. #endif
  20. /*如果是指针共享,那就只是减少mm->mm_users,
  21. 如果有独立的进程空间,那就直接释放页表,mm_struct,vm_struct
  22. 以及所有的vma*/
  23. __exit_mm(tsk);
  24. //加锁
  25. lock_kernel();
  26. //如果调用exit()之前该信号量还没退出,那就把它撤销
  27. sem_exit();
  28. //如果只是指针共享,那就减少files_struct->count,如果是独享,那就销毁
  29. __exit_files(tsk);
  30. //以上相同,释放fs->count
  31. __exit_fs(tsk);
  32. //释放信号处理函数表
  33. exit_sighand(tsk);
  34. //空函数
  35. exit_thread();
  36. ///表示进程是否为会话主管
  37. if (current->leader)
  38. disassociate_ctty(1);//删除终端,释放tty
  39. //若正在执行的代码是符合iBCS2标准的程序,则减少相对应模块的引用数目
  40. put_exec_domain(tsk->exec_domain);
  41. /* 若正在执行的代码属于全局执行文
  42. 件结构格则减少相对应模块的引用数目 */
  43. if (tsk->binfmt && tsk->binfmt->module)
  44. __MOD_DEC_USE_COUNT(tsk->binfmt->module);
  45. tsk->exit_code = code;
  46. //将当前进程设置为僵死状态;并给父进程发信号;其当前进程的子进程的父进程设置为init进程或者其他线程
  47. exit_notify();
  48. schedule();
  49. BUG();
接着挨个分析释放资源相关函数(信号量就等到进程间通信学完再分析)
  1. static inline void __exit_mm(struct task_struct * tsk)
  2. {
  3. struct mm_struct * mm = tsk->mm;//获取当前进程的内存描述符
  4. mm_release();//唤醒睡眠的父进程
  5. if (mm) {
  6. atomic_inc(&mm->mm_count);
  7. if (mm != tsk->active_mm) BUG();//确保mm与active_mm一样
  8. /* more a memory barrier than a real lock */
  9. task_lock(tsk);
  10. tsk->mm = NULL;//设置为NULL
  11. task_unlock(tsk);
  12.      //刷新tlb
  13. enter_lazy_tlb(mm, current, smp_processor_id());
  14. mmput(mm);//释放页表等等
  15. }
  16. }
以上资源释放完后,进程设置为僵尸状态,还保留pcb以及内核栈,自己并不释放而是由父进程负责,将调用exit_notify()通知其父进程
原因:让父进程可以统计信息,接下来看看exit_notify()
  1. /*
  2. * Send signals to all our closest relatives so that they know
  3. * to properly mourn us..
  4. */
  5. static void exit_notify(void)
  6. {
  7. struct task_struct * p, *t;
  8. //其当前进程的子进程的父进程设置为init进程,如果父进程是线程,那就托孤给其他线程
  9. forget_original_parent(current);
  10. /*
  11. * Check to see if any process groups have become orphaned
  12. * as a result of our exiting, and if they have any stopped
  13. * jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
  14. *
  15. * Case i: Our father is in a different pgrp than we are
  16. * and we were the only connection outside, so our pgrp
  17. * is about to become orphaned.
  18. */
  19. t = current->p_pptr;//获取其养父
  20. /*
  21. 如果当前进程与父进程属于相同的会话,又处于不同的组,当前进程挂了,整个组如果成了孤儿组,那就要
  22. 给这个进程组的所有进程发送一个SIGHUP跟SIGCONT信号
  23. */
  24. if ((t->pgrp != current->pgrp) &&//组不同而会话相同
  25. (t->session == current->session) &&
  26. will_become_orphaned_pgrp(current->pgrp, current) &&//判断是否是孤儿进程组
  27. has_stopped_jobs(current->pgrp)) {////如果进程组中有处于TASK_STOP状态的进程
  28. kill_pg(current->pgrp,SIGHUP,1);//先发送SIGHUP在发送SIGCONT
  29. kill_pg(current->pgrp,SIGCONT,1);
  30. }
  31. /* Let father know we died
  32. *
  33. * Thread signals are configurable, but you aren't going to use
  34. * that to send signals to arbitary processes.
  35. * That stops right now.
  36. *
  37. * If the parent exec id doesn't match the exec id we saved
  38. * when we started then we know the parent has changed security
  39. * domain.
  40. *
  41. * If our self_exec id doesn't match our parent_exec_id then
  42. * we have changed execution domain as these two values started
  43. * the same after a fork.
  44. *
  45. */
  46. if(current->exit_signal != SIGCHLD &&
  47. ( current->parent_exec_id != t->self_exec_id ||
  48. current->self_exec_id != current->parent_exec_id)
  49. && !capable(CAP_KILL))
  50. current->exit_signal = SIGCHLD;//给父进程发的信号是SIGCHLD /*
  51. * This loop does two things:
  52. *
  53. * A. Make init inherit all the child processes
  54. * B. Check to see if any process groups have become orphaned
  55. * as a result of our exiting, and if they have any stopped
  56. * jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
  57. */
  58. write_lock_irq(&tasklist_lock);
  59. current->state = TASK_ZOMBIE;//设置为僵尸进程
  60. do_notify_parent(current, current->exit_signal);//由父进程来料理后事
  61. //将子进程队列中的每个进程都转移到托孤的父进程的子进程队列中去
  62. while (current->p_cptr != NULL) {//p_cptr表示子进程
  63. p = current->p_cptr;//p指向子进程
  64. current->p_cptr = p->p_osptr;//子进程指向子进程他哥,形成一个队列
  65. p->p_ysptr = NULL;//子进程的滴滴设置为0
  66. p->ptrace = 0;
  67. p->p_pptr = p->p_opptr;//将养父改为亲父
  68. p->p_osptr = p->p_pptr->p_cptr;//子进程的哥哥改为子进程的养父的子进程,移到子进程队列
  69. if (p->p_osptr)
  70. p->p_osptr->p_ysptr = p;
  71. p->p_pptr->p_cptr = p;
  72. if (p->state == TASK_ZOMBIE)//并且判断每个子进程是否是僵尸状态
  73. do_notify_parent(p, p->exit_signal);
  74. /*
  75. * process group orphan check
  76. * Case ii: Our child is in a different pgrp
  77. * than we are, and it was the only connection
  78. * outside, so the child pgrp is now orphaned.
  79. 孤儿进程组: 一个进程组中的所有进程的父进程要么是该进程组的一个进程,
  80. 要么不是该进程组所在的会话中的进程。 一个进程组不是孤儿进程组的条件是,
  81. 该组中有一个进程其父进程在属于同一个会话的另一个组中。
  82. */
  83. if ((p->pgrp != current->pgrp) &&
  84. (p->session == current->session)) {
  85. int pgrp = p->pgrp;
  86. write_unlock_irq(&tasklist_lock);
  87. //父进程所在的组是否是孤儿进程组,以及是否含有stop进程
  88. if (is_orphaned_pgrp(pgrp) && has_stopped_jobs(pgrp)) {
  89. kill_pg(pgrp,SIGHUP,1);
  90. kill_pg(pgrp,SIGCONT,1);
  91. }
  92. write_lock_irq(&tasklist_lock);
  93. }
  94. }
  95. write_unlock_irq(&tasklist_lock);
  96. }
子进程托孤给其他进程(如果该进程是线程,也就是含有其他线程),否则托孤给init进程
  1. /*
  2. * When we die, we re-parent all our children.
  3. * Try to give them to another thread in our process
  4. * group, and if no such member exists, give it to
  5. * the global child reaper process (ie "init")
  6. */
  7. static inline void forget_original_parent(struct task_struct * father)
  8. {
  9. struct task_struct * p, *reaper;
  10. read_lock(&tasklist_lock);
  11. /* 获取当前用户空间的下一线程 */
  12. reaper = next_thread(father);
  13. if (reaper == father)//如果相等说明是进程,不是线程组,那就只能托孤给init进程
  14. reaper = child_reaper;//init进程
  15. for_each_task(p) {
  16. if (p->p_opptr == father) {//搜索所有task_struct数据结构,发现其进程生父就是要退出的进程
  17. /* We dont want people slaying init */
  18. p->exit_signal = SIGCHLD;//设置发送信号
  19. p->self_exec_id++;
  20. p->p_opptr = reaper;//将要死的进程的子进程托孤给reaper(当前线程的其他线程或者init进程?
  21. if (p->pdeath_signal) 
  22.             send_sig(p->pdeath_signal, p, 0);//发送信号,告知儿子死了
  23. }
  24. }
  25. read_unlock(&tasklist_lock);
  26. }


接下来查看do_notify_parent发送信号给父进程
  1. /*
  2. * Let a parent know about a status change of a child.
  3. 让一个父亲知道有关儿子的改变
  4. 参数为当前要退出进程,以及信号
  5. */
  6. void do_notify_parent(struct task_struct *tsk, int sig)
  7. {
  8. struct siginfo info;
  9. int why, status;
  10. info.si_signo = sig;
  11. info.si_errno = 0;
  12. info.si_pid = tsk->pid;
  13. info.si_uid = tsk->uid;
  14. /* FIXME: find out whether or not this is supposed to be c*time. */
  15. info.si_utime = tsk->times.tms_utime;
  16. info.si_stime = tsk->times.tms_stime;
  17. status = tsk->exit_code & 0x7f;
  18. why = SI_KERNEL; /* shouldn't happen */
  19. switch (tsk->state) {
  20. case TASK_STOPPED:
  21. /* FIXME -- can we deduce CLD_TRAPPED or CLD_CONTINUED? */
  22. if (tsk->ptrace & PT_PTRACED)
  23. why = CLD_TRAPPED;
  24. else
  25. why = CLD_STOPPED;
  26. break;
  27. default:
  28. if (tsk->exit_code & 0x80)
  29. why = CLD_DUMPED;
  30. else if (tsk->exit_code & 0x7f)
  31. why = CLD_KILLED;
  32. else {
  33. why = CLD_EXITED;
  34. status = tsk->exit_code >> 8;
  35. }
  36. break;
  37. }
  38. info.si_code = why;
  39. info.si_status = status;
  40. send_sig_info(sig, &info, tsk->p_pptr);//发送信号
  41. wake_up_parent(tsk->p_pptr);//唤醒父进程
  42. }


  1. /*
  2. * This function is typically called only by the session leader, when
  3. * it wants to disassociate itself from its controlling tty.
  4. *
  5. * It performs the following functions:
  6. * (1) Sends a SIGHUP and SIGCONT to the foreground process group
  7. * (2) Clears the tty from being controlling the session
  8. * (3) Clears the controlling tty for all processes in the
  9. * session group.
  10. *当前进程是一个会话的主进程(current->leader非0)那就还要将整个session与中断切断,并释放tty,pcb有个tty指针
  11. * The argument on_exit is set to 1 if called when a process is
  12. * exiting; it is 0 if called by the ioctl TIOCNOTTY.
  13. */
  14. void disassociate_ctty(int on_exit)
  15. {
  16. struct tty_struct *tty = current->tty;//获取当前进程的tty
  17. struct task_struct *p;
  18. int tty_pgrp = -1;
  19. if (tty) {
  20. tty_pgrp = tty->pgrp;//获取进程组的tty
  21. if (on_exit && tty->driver.type != TTY_DRIVER_TYPE_PTY)//统计tty设备打开的次数
  22. tty_vhangup(tty);
  23. } else {
  24. if (current->tty_old_pgrp) {
  25. kill_pg(current->tty_old_pgrp, SIGHUP, on_exit);//给当前进程组发送sighup与sigcont信号
  26. kill_pg(current->tty_old_pgrp, SIGCONT, on_exit);
  27. }
  28. return;
  29. }
  30. if (tty_pgrp > 0) {
  31. kill_pg(tty_pgrp, SIGHUP, on_exit);
  32. if (!on_exit)
  33. kill_pg(tty_pgrp, SIGCONT, on_exit);
  34. }
  35. current->tty_old_pgrp = 0;//进程控制终端所在的组标识设置为0
  36. tty->session = 0;//会话设置为0
  37. tty->pgrp = -1;//组设置为-1
  38. read_lock(&tasklist_lock);
  39. for_each_task(p)//遍历每个进程是否位于同一会话
  40. if (p->session == current->session)//当前进程是会话的主进程
  41. p->tty = NULL;//切断tty终端
  42. read_unlock(&tasklist_lock);
  43. }
do_exit流程:
禁止中断调用,0号进程,1号进程退出
如果有独立空间那就删除独立空间,释放页表,释放信号量,释放文件对象,释放信号处理函数表
如果是会话控制进程,删除终端,释放tty,接下来调用exit_notify()函数
如果当前进程是是线程(也就包含其他线程,非独享),托孤给其他线程,否则托孤给init进程
判断当前进程退出是否会导致孤儿进程组出现
设置发送信号为SIGCHLD,将当前进程设置为僵尸状态,接着调用do_notify_parent发送信号给父进程,并唤醒父进程
并将僵尸进程的所有子进程的队列移到托孤的队列.最后shedule()


  1. //等待子进程的pid
  2. asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)
  3. {
  4. int flag, retval;
  5. DECLARE_WAITQUEUE(wait, current);//为当前进程分配一个waitqueue结构
  6. struct task_struct *tsk;
  7. if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL))
  8. return -EINVAL;
  9. //添加到当前进程的waitchldexit对列中
  10. add_wait_queue(&current->wait_chldexit,&wait);
  11. repeat:
  12. flag = 0;
  13. current->state = TASK_INTERRUPTIBLE;//设置为睡眠,让其他进程先运行,等待子进程挂了
  14. read_lock(&tasklist_lock);
  15. tsk = current;
  16. do {
  17. struct task_struct *p;
  18. for (p = tsk->p_cptr ; p ; p = p->p_osptr) {//p表示当前进程的子进程
  19. if (pid>0) {
  20. if (p->pid != pid)//是否等于参数pid,不等于就继续
  21. continue;
  22. } else if (!pid) {//不是0号进程
  23. if (p->pgrp != current->pgrp)
  24. continue;
  25. } else if (pid != -1) {//不是-1(随便)
  26. if (p->pgrp != -pid)
  27. continue;
  28. }
  29. /* Wait for all children (clone and not) if __WALL is set;
  30. * otherwise, wait for clone children *only* if __WCLONE is
  31. * set; otherwise, wait for non-clone children *only*. (Note:
  32. * A "clone" child here is one that reports to its parent
  33. * using a signal other than SIGCHLD.) */
  34. //判断子进程的信号是否是sigchld
  35. if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0))
  36. && !(options & __WALL))
  37. continue;
  38. flag = 1;//表示是当前进程的子进程
  39. switch (p->state) {
  40. case TASK_STOPPED://等待子进程被跟踪
  41. if (!p->exit_code)//是否设置了退出码
  42. continue;
  43. if (!(options & WUNTRACED) && !(p->ptrace & PT_PTRACED))//判断条件是否跟踪
  44. continue;
  45. read_unlock(&tasklist_lock);
  46. retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
  47. if (!retval && stat_addr)
  48. retval = put_user((p->exit_code << 8) | 0x7f, stat_addr);
  49. if (!retval) {
  50. p->exit_code = 0;
  51. retval = p->pid;
  52. }
  53. goto end_wait4;//满足直接跳到end_wait4
  54. case TASK_ZOMBIE://僵尸状态
  55. current->times.tms_cutime += p->times.tms_utime + p->times.tms_cutime;
  56. current->times.tms_cstime += p->times.tms_stime + p->times.tms_cstime;
  57. read_unlock(&tasklist_lock);
  58. retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
  59. if (!retval && stat_addr)
  60. retval = put_user(p->exit_code, stat_addr);//指定位置保存退出码
  61. if (retval)
  62. goto end_wait4;
  63. retval = p->pid;
  64. if (p->p_opptr != p->p_pptr) {//生父与养父是否相同
  65. write_lock_irq(&tasklist_lock);
  66. REMOVE_LINKS(p);//将task_struct从养父队列中脱离出来
  67. p->p_pptr = p->p_opptr;//将养父设置为生父
  68. SET_LINKS(p);
  69. do_notify_parent(p, SIGCHLD);//通知生父进程自己挂了
  70. write_unlock_irq(&tasklist_lock);
  71. } else
  72. release_task(p);//释放残留的资源如pcb等等
  73. goto end_wait4;//子进程处于僵死状态,goto end_wait4
  74. default:
  75. continue;
  76. }
  77. }
  78. if (options & __WNOTHREAD)//如果设置了wnothread直接跳出
  79. break;
  80. tsk = next_thread(tsk);//到同一进程的寻找下一个线程,一线程创建的子进程挂了,其他线程调用wait应该没用吧?
  81. } while (tsk != current);
  82. read_unlock(&tasklist_lock);
  83. if (flag) {//如果pid不是当前进程的子进程,直接到end_wait4
  84. retval = 0;
  85. if (options & WNOHANG)//设置了wnohang
  86. goto end_wait4;
  87. retval = -ERESTARTSYS;
  88. if (signal_pending(current))//当前进程是否有信号未处理
  89. goto end_wait4;
  90. schedule();//被调度.等待被子进程唤醒
  91. goto repeat;
  92. }
  93. retval = -ECHILD;
  94. end_wait4:
  95. current->state = TASK_RUNNING;//将当前进程改为可运行状态
  96. remove_wait_queue(&current->wait_chldexit,&wait);
  97. return retval;
  98. }

下列条件之一得到满足时才结束,goto end_wait4:

1、所等待的子进程的状态变成TASK_STOPPED,TASK_ZOMBIE;

2、所等待的子进程存在,可不在上述两个状态,而调用参数options中的WHONANG标志位为1,或者当前进程接受到了其他的信号;

3、进程号pid的那个进程根本不存在,或者不是当前进程的子进程。

否则,当前进程将其自身的状态设成TASK_INTERRUPTIBLE,并调用schedule()。


释放残余的子进程资源
  1. static void release_task(struct task_struct * p)//释放子进程留下的资源
  2. {
  3. if (p != current) {
  4. #ifdef CONFIG_SMP
  5. /*
  6. * Wait to make sure the process isn't on the
  7. * runqueue (active on some other CPU still)
  8. */
  9. for (;;) {
  10. task_lock(p);
  11. if (!p->has_cpu)
  12. break;
  13. task_unlock(p);
  14. do {
  15. barrier();
  16. } while (p->has_cpu);
  17. }
  18. task_unlock(p);
  19. #endif
  20. atomic_dec(&p->user->processes);//子进程数目减少
  21. free_uid(p->user);//是否uid
  22. unhash_process(p);//把子进程的pcb从队列摘下来
  23. release_thread(p);//检查进程的LDT是否已释放
  24. current->cmin_flt += p->min_flt + p->cmin_flt;
  25. current->cmaj_flt += p->maj_flt + p->cmaj_flt;
  26. current->cnswap += p->nswap + p->cnswap;
  27. /*
  28. * Potentially available timeslices are retrieved
  29. * here - this way the parent does not get penalized
  30. * for creating too many processes.
  31. *
  32. * (this cannot be used to artificially 'generate'
  33. * timeslices, because any timeslice recovered here
  34. * was given away by the parent in the first place.)
  35. */
  36. current->counter += p->counter;
  37. if (current->counter >= MAX_COUNTER)
  38. current->counter = MAX_COUNTER;
  39. free_task_struct(p);//将2个物理页大小的pcb释放
  40. } else {
  41. printk("task releasing itself\n");
  42. }
  43. }








linux内核情景分析之exit与Wait的更多相关文章

  1. linux内核情景分析之execve()

    用来描述用户态的cpu寄存器在内核栈中保存情况.可以获取用户空间的信息 struct pt_regs { long ebx; //可执行文件路径的指针(regs.ebx中 long ecx; //命令 ...

  2. Linux内核情景分析之消息队列

    早期的Unix通信只有管道与信号,管道的缺点: 所载送的信息是无格式的字节流,不知道分界线在哪,也没通信规范,另外缺乏控制手段,比如保温优先级,管道机制的大小只有1页,管道很容易写满而读取没有及时,发 ...

  3. Linux内核情景分析之异常访问,用户堆栈的扩展

    情景假设: 在堆内存中申请了一块内存,然后释放掉该内存,然后再去访问这块内存.也就是所说的野指针访问. 当cpu产生页面错误时,会把失败的线性地址放在cr2寄存器.线性地址缺页异常的4种情况 1.如果 ...

  4. Linux内核情景分析的alloc_pages

    NUMA结构的alloc_pages ==================== mm/numa.c 43 43 ==================== 43 #ifdef CONFIG_DISCON ...

  5. linux内核情景分析之内核中的互斥操作

    信号量机制: struct sempahore是其结构,定义如下 struct semaphore { atomic_t count;//资源数目 int sleepers;//等待进程数目 wait ...

  6. linux内核情景分析之命名管道

    管道是一种"无名","无形文件,只可以近亲进程使用,不可以再任意两个进程通信使用,所以只能实现"有名","有形"的文件来实现就可以 ...

  7. linux内核情景分析之信号实现

    信号在进程间通信是异步的,每个进程的task_struct结构有一个sig指针,指向一个signal_struct结构 定义如下 struct signal_struct { atomic_t cou ...

  8. linux内核情景分析之强制性调度

    从系统调用返回到用户空间是否调度,从ret_with_reschedule可看出,是否真正调度,取决于当前进程的pcb中的need_resched是否设置为1,那如何设置为1取决于以下几种情况: 时间 ...

  9. linux内核情景分析之匿名管道

    管道的机制由pipe()创建,由pipe()所建立的管道两端都在同一进程.所以必须在fork的配合下,才可以在具有亲缘关系的进程通信 /* * sys_pipe() is the normal C c ...

随机推荐

  1. Dropping Balls(小球下落)

    紫书P148,例题6-6 Sample Input 4 2 3 4 10 1 2 2 8 128 Sample Output 12 7 512 3 255 这应该不仅仅是一棵完全二叉树,题目中说保证所 ...

  2. [CodeForces948C]Producing Snow(优先队列)

    Description 题目链接 Solution 将温度做一个前缀和,用一个优先队列依次处理一遍 思路还是很简单的 Code #include <cstdio> #include < ...

  3. 理解线程3 c语言示例线程基本操作

    Table of Contents 1. 基本线程的动作 1.1. 设置线程属性 1.1.1. 设置脱离状态 1.1.2. 设置调度属性 1.2. 取消线程 1.3. 主线程创建多个线程示例 2. 了 ...

  4. saltstack执行远程命令

    目录 Remote Execution salt state salt state 系统 salt state 系统流程 Runner salt runner Orchestrate Runner S ...

  5. 12,DBUtils - Python数据库连接池

    创建数据库连接池: import time import pymysql import threading from DBUtils.PooledDB import PooledDB, SharedD ...

  6. 10,knn手写数字识别

    # 导包 import numpy as np import matplotlib.pyplot as plt from sklearn.neighbors import KNeighborsClas ...

  7. 16,docker入门

      在学一门新知识的时候,超哥喜欢提问,why?what?how? wiki资料 什么是docker Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一 ...

  8. laravel5.5表单验证

    1. 在第一次验证失败后停止 有时,你希望在某个属性第一次验证失败后停止运行验证规则.为了达到这个目的,附加 bail 规则到该属性: $this->validate($request, [ ' ...

  9. PHP基础壹

    <?php //<!--//注释方式-->//<!--//echo 后面跟字符串:-->//<!--print("123");-->//& ...

  10. java课后作业2017.10.20

    动手动脑1: public class Test{ public static void main(String args[]) { Foo obj1=new Foo(); }}class Foo{ ...