在linux内核中管理进程的方式,是创建了一个双向循环链表,然后这个链接上每一个成员就是一个task_struct结构体,每一个结构体就是一个进程,这玩意,太大了,这里我的精神不允许我叭叭那么多,所以我就调几个成员说,主要就是描述这个进程中的状态标志啊,pid号啊,优先级啊,他爹是谁(父进程),打开的文件,进程地址空间,挂起的信号,进程的状态等等。要知道一个task_struct占用大概1.8kb大小呢,感觉还是挺大的,但是他管理的是一个系统的一个进程,相对来说,也不大。
但是在内核中分配进程描述符并不是乱分配的,它使用的是一个叫slab算法来分配的(这里可以自行百度slab算法)
在linux系统每一个进程都是独一无二的,所以说必须有一个变量代表他,以便于linux调度,每一个进程都有一个pid,这个pid是一个int型变量,这个东西是可以配置的,当然他有一个默认值,默认值就是int型最大值,内核把他放到这个描述符中,就是那个task_struct结构体,在普遍产品中这个值是够的,但是这个值对于我们做产品来说 还是小一点好,这样子在内核进行轮训的时候 才会变快,进程数越小就越快嘛,但是有的应用场景就不行了要是弄一个大型服务器可能就不够了,所以我们可以修改这个值:
查看最大值的方式:cat /proc/sys/kernel/pid_max设置这个最大值的方式: echo 1234567 > /proc/sys/kernel/pid_max
如果我们在内核中需要需要到当前运行的进程的中的变量的话,那么请使用宏 current 这个宏就是当前内核中运行的进程的描述符。
在进程描述符中有一个成员是state,他表示当前进程的状态,在内核运行期间,每个进程必然是有一个自己的运行状态,分别如下:运行 :正在执行,等待执行的进程
可中断:正在休眠,被阻塞住的,等待信号唤醒的进程。
不可中断:必须等待特定的信号才会被唤醒的进程,ps的时候里面带D的,你会发现你kill不掉他,就是这玩意。
停止:没有投入运行,也不能投入运行的进程,一般都是被信号暂停的进程。
a
内核在调度进程的过程中需要设置进程的状态使用的函数是set_task_state(task.state);
对于一个进程来说,除了init进程其余的都是有爸爸的,只是看他爸爸后台硬不硬而已,而且在父进程注销掉后,它旗下的所有子进程内核会重新给他安排个爸爸,是不是很奇怪,干爹就是这么来的。
但是所有的进程的父进程都是pid为1的init进程,这个init进程是读取系统的初始化脚本,并且执行相关程序,最终完成整个系统的启动。
在每个进程描述符中都一个parent的指针和一个children的指针,所以这就代表着不同的进程可以有一个爹。因为有这个指针我们就可以遍历这个链表找到这个进程树下的任何一个进程描述符。
进程的创建方式在用户空间实际上就一种方式就是fork,只不过他有很多变种函数,最终执行的都是一个clone函数,这个fork函数就是写时拷贝的体现,他并不是把父进程的所有地址空间都拷贝,而是让父子进程共用一个拷贝,而且只有在需要写入的时候,数据才会被复制,在这之前实际上只是资源只读共享,如果需要使用的时候才会复制,然后改写。
fork执行步骤:
这里提一下linux线程,实际上线程就是进程,在linux里面没有线程的说法,创建线程的方法最后同样也是调用clone函数,只是他会将当前进程得资源进行共享,所以传递的参数与进程有一些不一样而已,clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);参数标志含义
CLONE_FILES父子进程共享打开的文件
CLONE_FS父子进程共享文件系统信息
CLONE_IDLETASK将pid设置为0
CLONE_NEWNE为子进程创建新的命令空间
CLONE_PARENT制定子进程与父进程拥有同一个父进程
CLONE_PTRACE继续调试子进程
CLONE_SETTID将TID会写到用户空间
CLONE_SETTLS为子进程创建新的TLS
CLONE_SIGHAND父子进程共享信号处理函数以及被阻断的信号
CLONE_SYSVSEM父子进程共享SYstem v SEM_UNDO定义
CLONE_THREAD父子进程进入相同的线程组
CLONE_VFORK调用vfork,所以父进程准备睡眠等待子进程将它唤醒
CLONE_UNTRACED防止跟踪进程在子进程上强制执行CLONE_PTRACE
CLONE_STOP以TASK_STOPPED状态开始进程
CLONE_SETTLS为子进程创建新的TLS
CLONE_CHILE_CLEARTID清除子进程的TID
CLONE_CHILE_SETTID设置子进程的TID
CLONE_PARENT_SETTID设置父进程的TID
CLONE_VM父子进程共享地址空间
内核线程实际上跟用户空间的进程唯一的差别就是没有独立的地址空间,对于用户层空间的一个进程来说他认为自己占用4G的虚拟内存空间,但是内核线程没有,在内核中创建线程的函数如下:
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data , const char namefmt[],...);
进程终结,就意味着他要释放掉他占用的资源并且把这个消息告诉他爸爸,但是实际上进程的析构(学过面向对象的都知道就是了结了自己要做的操作,类似于close)都是资深引起的,他发生在自己调用exit()时,最后都是调用到内核层的do_exit()函数,他的流程如下,上图!
总结:这里主要讲解的是进程在内核中从开始到结束的一系列过程,进程是怎么通过clone函数创建的,父进程是怎么回去子进程的属性的,进程是怎么消亡的,以及线程和进程之间的关系。