进程退出,大概可以分为三种方式:运行完后正常退出,发生某种异常如访问非法内存,除零等的异常退出,被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函数调用,其调用关系如下所示:
![linux进程退出后操作系统是如何删除这个进程对应的内核资源的 linux进程退出后操作系统是如何删除这个进程对应的内核资源的](https://i-blog.csdnimg.cn/blog_migrate/9bfd6f3e0ae052d3cb6545330e691a93.jpeg)
由于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等等。