linux进程退出后操作系统是如何删除这个进程对应的内核资源的

进程退出,大概可以分为三种方式:

  1. 运行完后正常退出
  2. 发生某种异常如访问非法内存
  3. 除零等的异常退出,被kill掉而退出的

作为程序的main,它的原型应该是:int main(int argc, char argv[])

虽然以前也能用void main(int argc, char argv[]) ,如在VC6中可以编译通过,但是在新一些的编译器如gcc3.2和g++中,要么是会产生警告,要么就是编译通不过。这是因为新的标准规定了main函数应该返回整型数。

在正常的编译时,编译器都会在编出的可执行文件main函数的后面加上exit函数。而当进程发生异常时,回调用abort函数,这两个都是glibc的函数,而当使用kill命令等方式终止进程的运行时,操作系统linux会调用do_signal内核函数,调用关系如下所示:

完成这个进程在内核中资源的回收就是由这个do_exit函数完成的。下面我们来详细看一下这个函数。

这个函数分别调用了exit_mm, exit_sem, __exit_files, __exit_fs, exit_namespace, exit_thread, cpuset_exitexit_keys等等,其中__exit_files函数所完成的工作就是回收这个进程文件系统相关的资源,因为在linux系统中,一个进程所打开的所有的文件包含各种设备,socket等都是用文件描述符来对应,下面我们就详细看一下这个函数。

首先看一下这个函数的详细调用关系,如下所示:

到这个时候,则就要依据这个文件句柄的具体是啥来决定调用哪个函数。下面以socket句柄为例来做介绍。

Linux文件系统中定义的文件操作数据结构 (fs.h)如下:

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
        ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
        ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*dir_notify)(struct file *filp, unsigned long arg);
        int (*flock) (struct file *, int, struct file_lock *);
};

在socket.c文件中定义的对应socket的文件操作的这个结构值如下:

static struct file_operations socket_file_ops = {
        .owner =       THIS_MODULE,
        .llseek =      no_llseek,
        .aio_read =    sock_aio_read,
        .aio_write =   sock_aio_write,
        .poll =        sock_poll,
        .unlocked_ioctl = sock_ioctl,
        .mmap =        sock_mmap,
        .open =        sock_no_open, 
        .release =     sock_close,
        .fasync =      sock_fasync,
        .readv =       sock_readv,
        .writev =      sock_writev,
        .sendpage =    sock_sendpage
};

从中可以看出并不是这个文件操作结构中的每一个子变量在socket中都有定义的。当通过socket函数调用,其调用关系如下所示:

由于socket本身也是一个通用的架构,可以支持各种各样的通讯协议,第一个函数sock_create则是为socket本身的通用架构服务的,并会调用到相应的协议定义的socket创建函数。

而第二函数sock_map_fd则是构建一个从socket到文件句柄的一个映射,其函数原型如下:int sock_map_fd(struct socket *sock)

这个函数里的如下这个行代码就是把socket定义的文件操作赋给这个映射的文件句柄中去:file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops;

这样如果进程创建了socket的话,当它退出的时候,它就会调用socket所对应的sock_close来释放socket所占有的内核资源。以前面讨论的TIPC协议,当一个应用程序创建了tipc的socket,当这个应用程序的进程被kill掉的情况下,其调用关系如下:

这样这个进程中在内核中创建tipc协议通讯使用内核资源就都被删除,如tipc的port等等。

http://blog.sina.com.cn/s/blog_605507340101dcw6.html


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!