2号进程

内核初始化rest_init函数中,由进程 0 (swapper 进程)创建了两个process

  • init 进程 (pid = 1, ppid = 0)
  • kthreadd (pid = 2, ppid = 0)

所有其它的内核线程的ppid 都是 2,也就是说它们都是由kthreadd thread创建的

所有的内核线程在大部分时间里都处于阻塞状态(TASK_INTERRUPTIBLE)只有在系统满足进程需要的某种资源的情况下才会运行

它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

2号进程的创建

在rest_init函数中创建2号进程的代码如下

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);

2号进程的事件循环

int kthreadd(void *unused)
{
struct task_struct *tsk = current; /* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, cpu_all_mask); // 允许kthreadd在任意CPU上运行
set_mems_allowed(node_states[N_MEMORY]); current->flags |= PF_NOFREEZE; for (;;) {
/* 首先将线程状态设置为 TASK_INTERRUPTIBLE, 如果当前
没有要创建的线程则主动放弃 CPU 完成调度.此进程变为阻塞态*/
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list)) // 没有需要创建的内核线程
schedule(); // 什么也不做, 执行一次调度, 让出CPU /* 运行到此表示 kthreadd 线程被唤醒(就是我们当前)
设置进程运行状态为 TASK_RUNNING */
__set_current_state(TASK_RUNNING); spin_lock(&kthread_create_lock); // 加锁,
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create; /* 从链表中取得 kthread_create_info 结构的地址,在上文中已经完成插入操作(将
kthread_create_info 结构中的 list 成员加到链表中,此时根据成员 list 的偏移
获得 create) */
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list); /* 完成穿件后将其从链表中删除 */
list_del_init(&create->list); /* 完成真正线程的创建 */
spin_unlock(&kthread_create_lock); create_kthread(create); spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
} return 0;
}

kthreadd的核心是一for和while循环体。

在for循环中,如果发现kthread_create_list是一空链表,则调用schedule调度函数,因为此前已经将该进程的状态设置为TASK_INTERRUPTIBLE,所以schedule的调用将会使当前进程进入睡眠。

如果kthread_create_list不为空,则进入while循环,在该循环体中会遍历该kthread_create_list列表,对于该列表上的每一个entry,都会得到对应的类型为struct kthread_create_info的节点的指针create.

然后函数在kthread_create_list中删除create对应的列表entry,接下来以create指针为参数调用create_kthread(create).

create_kthread的过程如下

create_kthread完成内核线程创建

static void create_kthread(struct kthread_create_info *create)
{
int pid; #ifdef CONFIG_NUMA
current->pref_node_fork = create->node;
#endif
/* We want our own signal handler (we take no signals by default).
其实就是调用首先构造一个假的上下文执行环境,最后调用 do_fork()
返回进程 id, 创建后的线程执行 kthread 函数
*/
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {
/* If user was SIGKILLed, I release the structure. */
struct completion *done = xchg(&create->done, NULL); if (!done) {
kfree(create);
return;
}
create->result = ERR_PTR(pid);
complete(done);
}
}

在create_kthread()函数中,会调用kernel_thread来生成一个新的进程,该进程的内核函数为kthread,调用参数为

pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

我们可以看到,创建的内核线程执行的事件kthread

此时回到 kthreadd thread,它在完成了进程的创建后继续循环,检查 kthread_create_list 链表,如果为空,则 kthreadd 内核线程昏睡过去

那么我们现在回想我们的操作

我们在内核中通过kernel_create或者其他方式创建一个内核线程, 然后kthreadd内核线程被唤醒, 来执行内核线程创建的真正工作,于是这里有三个线程

  1. kthreadd已经光荣完成使命(接手执行真正的创建工作),睡眠
  2. 唤醒kthreadd的线程由于新创建的线程还没有创建完毕而继续睡眠 (在 kthread_create函数中)
  3. 新创建的线程已经正在运行kthread,但是由于还有其它工作没有做所以还没有最终创建完成.

新创建的内核线程kthread函数

static int kthread(void *_create)
{
/* Copy data: it's on kthread's stack
create 指向 kthread_create_info 中的 kthread_create_info */
struct kthread_create_info *create = _create; /* 新的线程创建完毕后执行的函数 */
int (*threadfn)(void *data) = create->threadfn;
/* 新的线程执行的参数 */
void *data = create->data;
struct completion *done;
struct kthread self;
int ret; self.flags = 0;
self.data = data;
init_completion(&self.exited);
init_completion(&self.parked);
current->vfork_done = &self.exited; /* If user was SIGKILLed, I release the structure. */
done = xchg(&create->done, NULL);
if (!done) {
kfree(create);
do_exit(-EINTR);
}
/* OK, tell user we're spawned, wait for stop or wakeup
设置运行状态为 TASK_UNINTERRUPTIBLE */
__set_current_state(TASK_UNINTERRUPTIBLE); /* current 表示当前新创建的 thread 的 task_struct 结构 */
create->result = current;
complete(done);
/* 至此线程创建完毕 , 执行任务切换,让出 CPU */
schedule(); ret = -EINTR; if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data);
}
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);
}

线程创建完毕:

创建新 thread 的进程恢复运行 kthread_create() 并且返回新创建线程的任务描述符

新创建的线程由于执行了 schedule() 调度,此时并没有执行.

直到我们使用wake_up_process(p);唤醒新创建的线程

线程被唤醒后, 会接着执行threadfn(data)

ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data);
}
/* we can't just return, we must preserve "self" on stack */
do_exit(ret)

总结

kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理,它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

我们在内核中通过kernel_create或者其他方式创建一个内核线程, 然后kthreadd内核线程被唤醒, 来执行内核线程创建的真正工作,新的线程将执行kthread函数, 完成创建工作,创建完毕后让出CPU,因此新的内核线程不会立刻运行.需要手工 wake up, 被唤醒后将执行自己的真正工作函数

  • 任何一个内核线程入口都是 kthread()
  • 通过 kthread_create() 创建的内核线程不会立刻运行.需要手工 wake up.
  • 通过 kthread_create() 创建的内核线程有可能不会执行相应线程函数threadfn而直接退出

Linux下2号进程的kthreadd--Linux进程的管理与调度(七)的更多相关文章

  1. linux下1号进程的前世(kthread_init)今生(init)

    参考: 1.  Linux下1号进程的前世(kernel_init)今生(init进程)----Linux进程的管理与调度(六) 2. linux挂载根文件系统过程 3. BusyBox init工作 ...

  2. Linux下java nohup 后台运行关闭后进程停止的原因,不挂断后台运行命令

    Linux下java nohup 后台运行关闭后进程停止的原因,不挂断后台运行命令 今天写sh脚本发现一终止命令程序就停止运行了,检查了很久才发现后面少了个&字符导致的!错误写法:nohup ...

  3. 性能测试分析过程(三)linux下查看最消耗CPU/内存的进程

    linux下查看最消耗CPU  内存的进程 1.CPU占用最多的前10个进程:  ps auxw|head -1;ps auxw|sort -rn -k3|head -10  2.内存消耗最多的前10 ...

  4. linux下sort命令使用详解---linux将文本文件内容加以排序命令

    转载自:http://www.cnblogs.com/hitwtx/archive/2011/12/03/2274592.html linux下sort命令使用详解---linux将文本文件内容加以排 ...

  5. Linux下安装SVN,仓库创建,用户权限管理

    Exported from Notepad++           Linux下安装SVN,仓库创建,用户权限管理 1.SVN安装 Ubuntu系统下安装:sudoapt-getinstallsubv ...

  6. Linux下不借助工具实现远程linux服务器上传下载文件

    # Linux下不借助工具实现远程linux服务器上传下载文件 ## 简介 - Linux下自带ssh工具,可以实现远程Linux服务器的功能- Linux下自带scp工具,可以实现文件传输功能 ## ...

  7. Linux下1号进程的前世(kernel_init)今生(init进程)----Linux进程的管理与调度(六)

    前面我们了解到了0号进程是系统所有进程的先祖, 它的进程描述符init_task是内核静态创建的, 而它在进行初始化的时候, 通过kernel_thread的方式创建了两个内核线程,分别是kernel ...

  8. Linux下0号进程的前世(init_task进程)今生(idle进程)----Linux进程的管理与调度(五)【转】

    前言 Linux下有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2) idle进程由系统自动创建, 运行在内核态 idle进程其pi ...

  9. 由linux下的多进程编程引发的关于进程间隔离的思考

    源代码放到了三个文件中: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include & ...

随机推荐

  1. Zookeeper Client简介

    直接使用zk的api实现业务功能比较繁琐.因为要处理session loss,session expire等异常,在发生这些异常后进行重连.又因为ZK的watcher是一次性的,如果要基于wather ...

  2. react入门学习及总结

    前言 不知不觉一年又过去了,新的一年又到来,2019应该要好好思考,好好学点有用的东西,规划下自己今后的学习方向,不要再像以前那样感觉很迷茫. react简单介绍 官网及中文文档 https://re ...

  3. Pytorch1.0入门实战二:LeNet、AleNet、VGG、GoogLeNet、ResNet模型详解

    LeNet 1998年,LeCun提出了第一个真正的卷积神经网络,也是整个神经网络的开山之作,称为LeNet,现在主要指的是LeNet5或LeNet-5,如图1.1所示.它的主要特征是将卷积层和下采样 ...

  4. Docker介绍及常用操作演示(一)--技术流ken

    Docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化.容器是完全使用沙箱机制,相互 ...

  5. mysql+ssl主从复制

    一.作为主服务器Master, 会把自己的每一次改动都记录到 二进制日志 Binarylog 中. (从服务器I/O thread会负责来读取master binary log, 然后写入自身rela ...

  6. C#.NET和C++结构体Socket通信与数据转换

    最近在用C#做一个项目的时候,Socket发送消息的时候遇到了服务端需要接收C++结构体的二进制数据流,这个时候就需要用C#仿照C++的结 构体做出一个结构来,然后将其转换成二进制流进行发送,之后将响 ...

  7. 【Java深入研究】10、红黑树

    一.红黑树介绍 红黑树是二叉查找树,红黑树的时间复杂度为: O(lgn) 红黑树的特性:(1)每个节点或者是黑色,或者是红色.(2)根节点是黑色.(3)每个叶子节点(NIL)是黑色. [注意:这里叶子 ...

  8. JAVA JVM常见内存参数配置简析

    JVM常见内存参数配置简析   常见参数 -Xms .-Xmx.-XX:newSize.-XX:MaxnewSize.-Xmn(-XX:newSize.-XX:MaxnewSize) 简析 1.-Xm ...

  9. Java8 使用stream实现各种list操作

    利用java8新特性,可以用简洁高效的代码来实现一些数据处理. 定义1个Apple对象: public class Apple { private Integer id; private String ...

  10. idea项目git版本回退

    idea项目git版本回退 一.查询提交历史 项目上右键,点击Git,点击Show History 二.复制版本号 我这里有两个测试的版本,我的当前版本是[二],所以我选择[一],右键,选择Copy ...