zoukankan      html  css  js  c++  java
  • Linux进程管理(3):总结

     7. exit与_exit的差异
        为了理解这两个系统调用的差异,先来讨论文件内存缓存区的问题。 在linux中,标准输入输出(I/O)函数都是作为文件来处理。对应于打开的每个文件,在内存中都有对应的缓存,每次读取文件时,会多读一些记录到缓存中,这样在下次读文件时,就在缓存中读取;同样,在写文件时也是写在文件对应的缓存中,并不是直接写入硬盘的文件中,等满足了一定条件(如达到一定数量,遇到换行符 或文件结束标志EOF)才将数据真正的写入文件。这样做的好处就是加快了文件读写的速度。但这样也带来了一些问题,比如有一些数据,我们认为已经写入了文件,但实际上没有满足一定条件而任然驻留在内存的缓存中,这样,如果我们直接用_exit()函数直接终止进程,将导致数据丢失。如果改成exit,就不会有数据丢失的问题出现了,这就是它们之间的区别了.要解释这个问题,就要涉及它们的工作步骤了。
       exit():通过前面源代码分析可知,在执行该函数时,进程会检查文件打开情况,清理I/O缓存,如果缓存中有数据,就会将它们写入相应的文件,这样就防止了文件数据的丢失,然后终止进程。
       _exit():在执行该函数时,并不清理标准输入输出缓存,而是直接清除内存空间,当然也就把文件缓存中尚未写入文件的数据给销毁了。由此可见,使用exit()函数更加安全。
       此外,对于它们两者的区别还有各自的头文件不同。exit()在stdlib.h中,_exit()在unistd.h中。一般情况下exit(0)表示正常退出,exit(1),exit(-1)为异常退出,0、1、-1是返回值,具体含义可以自定。还要注意return是返回函数调用,如果返回的是main函数,则为退出程序 。exit是在调用处强行退出程序,运行一次程序就结束。

        下面是完整的Linux进程运行流程:

    [plain] view plain copy
    1. arch/x86/include/asm/unistd_32.h:fork()        用户空间来调用(如C程序)  
    2.     --->int $0×80        产生0x80软中断  
    3.     --->arch/x86/kernel/entry_32.S:ENTRY(system_call)  中断处理程序system_call()  
    4.         --->执行SAVE_ALL宏      保存所有CPU寄存器值  
    5.         --->arch/x86/kernel/syscall_table_32.S:ENTRY(sys_call_table) 系统调用多路分解表  
    6.     --->arch/x86/kernel/process_32.c:sys_fork()  
    7.         --->kernel/fork.c:do_fork()  复制原来的进程成为另一个新的进程  
    8.             --->kernel/fork.c:copy_process()  
    9.                 --->struct task_struct *p;  定义新的进程描述符(PCB)  
    10.                 --->clone_flags标志的合法性检查  
    11.                 --->security_task_create()    安全性检查(SELinux机制)  
    12.                 --->kernel/fork.c:dup_task_struct()   复制进程描述符  
    13.                     --->struct thread_info *ti;  定义线程信息结构  
    14.                     --->alloc_task_struct()      为新的PCB分配内存  
    15.                     --->kernel/fork.c:arch_dup_task_struct()  复制父进程的PCB  
    16.                     --->atomic_set(&tsk->usage,2)    将PCB使用计数器设置为2,表示活动状态  
    17.                 --->copy_creds()   复制权限及身份信息  
    18.                 --->检测进程总数是否超过max_threads  
    19.                 --->初始化PCB中各个字段  
    20.                 --->sched_fork()    调度器相关设置  
    21.                 --->复制进程所有信息copy_semundo(), copy_files(),  
    22.                 --->copy_signal(), copy_mm()  
    23.                 --->copy_thread()    复制线程  
    24.                 --->alloc_pid()    分配pid  
    25.                 --->更新属性和进程数量计数  
    26.             --->kernel/sched.c:wake_up_new_task()  把进程放到运行队列上,让调度器进行调度  
    27.                 --->kernel/sched.c:select_task_rq()  选择最佳的CPU(SMP中有多个CPU)  
    28.                 --->p->state = TASK_RUNNING    设置成TASK_RUNNING状态  
    29.                 --->activate_task()  
    30.                     --->enqueue_task()  把当前进程插入到对应CPU的runqueue上  
    31.             --->有CLONE_VFORK标志:wait_for_completion()  让父进程阻塞,等待子进程结束  
    32.             --->返回分配的pid  
    33. kernel/sched.c:schedule()    调度新创建的进程  
    34. 进程运行中  
    35. exit()        用户空间来调用(如C程序)  
    36.     --->0x80中断跳转到include/linux/syscalls.h:sys_exit()  
    37.         --->kernel/exit.c:do_exit()    负责进程的退出  
    38.             --->struct task_struct *tsk = current;    获取我的PCB  
    39.             --->set_fs(USER_DS)    设置使用的文件系统模式  
    40.             --->exit_signals()     清除信号处理函数并设置PF_EXITING标志  
    41.             --->清除进程一系列资源exit_mm(), exit_files()   
    42.             --->exit_fs(), exit_thread()  
    43.             --->kernel/exit.c:exit_notify()  退出通知  
    44.                 --->forget_original_parent()  把我的所有子进程过继给init进程  
    45.                 --->kill_orphaned_pgrp()      向进程组内各进程发送挂起信号SIGHUP及SIGCONT  
    46.                 --->tsk->exit_signal = SIGCHLD;  向我的父进程发送SIGCHLD信号  
    47.                 --->kernel/exit.c:do_notify_parent()  通知父进程  
    48.                     --->如果父进程处理SIGCHLD信号,返回DEATH_REAP  
    49.                     --->如果父进程不处理SIGCHLD信号,返回传入时的信号值  
    50.                     --->__wake_up_parent()    唤醒父进程  
    51.                 --->通知返回DEATH_REAP,设置exit_state为EXIT_DEAD    我退出并且死亡  
    52.                 --->否则设置我为EXIT_ZOMBIE      我退出但没死亡,成为僵尸进程  
    53.                 --->如果为DEATH_REAP:release_task()    我自己清理相关资源  
    54.                 --->如果为僵尸,在我的父进程退出时我会过继给init进程,由init负责清理  
    55.             --->exit_io_context()    清理IO上下文  
    56.             --->preempt_disable()    禁用抢占  
    57.             --->tsk->state = TASK_DEAD;    设置我为进程死亡状态  
    58.             --->kernel/sched.c:schedule()  释放我的PCB,调度另一个新的进程  
    59.   
    60. 清理僵尸进程:wait系统调用         等待子进程结束  
    61.     --->0x80中断最后到达kernel/exit.c:do_wait()  
    62.         --->do_wait_thread()  
    63.             --->wait_consider_task()  
    64.                 --->如果子进程为EXIT_DEAD,返回0,wait调用返回,子进程自己清理自己  
    65.                 --->如果子进程为EXIT_ZOMBIE:wait_task_zombie()  
    66.                     --->xchg()    设置僵尸子进程为EXIT_DEAD  
    67.                     --->release_task()    清理僵尸子进程  

        下面是基本的执行流程图:


    图1 Linux进程管理的执行流程
  • 相关阅读:
    python学习,day5:内置模块(hashlib,加密)
    python学习,day5:内置模块(configerparser文件的创建和修改)
    python学习,day5:内置模块(xml文件的创建和修改)
    python学习,day5:内置模块(xml文件的管理)
    python学习,day5:内置模块(复制和压缩)
    python学习,day5:内置模块(sys)
    python学习,day5:内置模块(os模块)
    AngularJS中Model和Controller传值问题
    接收JSON类型转成对象
    改数据库编码latin1为utf8
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/7053638.html
Copyright © 2011-2022 走看看