ホームページ >運用・保守 >Linuxの運用と保守 >Linux にはカーネルレベルのスレッドがありますか?

Linux にはカーネルレベルのスレッドがありますか?

青灯夜游
青灯夜游オリジナル
2022-11-11 14:20:322348ブラウズ

Linux にはカーネル レベルのスレッドがあり、Linux はカーネル レベルのマルチスレッドをサポートします。 Linux カーネルはサービス プロセス (ソフトウェアおよびハードウェア リソースの管理、ユーザー プロセスのさまざまなプロセスへの応答) と見なすことができ、カーネルは複数の実行ストリームを並行して必要とし、ブロッキングの可能性を防ぐためにマルチスレッドをサポートします。カーネル スレッドは、特定の処理に使用できるカーネルのクローンです。カーネルは、カーネル スレッドのスケジュールを担当します。1 つのカーネル スレッドがブロックされても、他のカーネル スレッドには影響しません。

Linux にはカーネルレベルのスレッドがありますか?

#このチュートリアルの動作環境: linux7.3 システム、Dell G3 コンピューター。

スレッドは通常、プロセス内のコードのさまざまな実行ルートとして定義されます。実装に関しては、スレッドには「ユーザーレベルのスレッド」と「カーネルレベルのスレッド」の 2 種類があります。

ユーザー スレッドは、カーネル サポートなしでユーザー プログラムに実装されたスレッドを指します。オペレーティング システムのコアには依存しません。アプリケーション プロセスは、スレッド ライブラリを使用して、スレッドの作成、同期、スケジュール、および管理のための機能を提供します。ユーザースレッドを制御します。この種のスレッドは DOS などのオペレーティング システムでも実装できますが、スレッドのスケジューリングはユーザー プログラムによって完了する必要があり、これは Windows 3.x の協調マルチタスクに似ています。

もう 1 つはカーネルの参加を必要とし、スレッドのスケジューリングを完了します。これはオペレーティング システムのコアに依存し、カーネルの内部ニーズによって作成および破棄されます。どちらのモデルにも独自の長所と短所があります。

ユーザー スレッドには追加のカーネル オーバーヘッドは必要なく、ユーザー モード スレッドの実装は、特別なアプリケーションの要件に合わせてカスタマイズまたは変更できます。ただし、スレッドが I/O により待機状態になっている場合は、 、プロセス全体はスケジューラによって待機状態に切り替えられ、他のスレッドは実行する機会が得られませんが、カーネル スレッドには制限がないため、マルチプロセッサの同時実行性を活用するのに役立ちますが、システム費用がさらにかかります。

Windows NT および OS/2 はカーネル スレッドをサポートします。 Linux はカーネルレベルのマルチスレッドをサポートしています。

Linux のカーネル レベルのスレッド

1. カーネル スレッドの概要

Linux カーネルは、サービス プロセス (ソフトウェアおよびハードウェア リソースを管理し、さまざまなユーザー プロセスに応答するプロセス) とみなすことができます。

カーネルは、ブロックの可能性を防ぐために、複数の実行ストリームを並行して必要とします。 、マルチスレッドをサポートします。

カーネル スレッドは、特定の処理に使用できるカーネルのクローンです。カーネルは、カーネル スレッドのスケジューリングを担当します。1 つのカーネル スレッドがブロックされても、他のカーネル スレッドには影響しません。 。

カーネル スレッドは、カーネル自体によって直接開始されるプロセスです。カーネル スレッドは実際には、カーネル機能を実行のために独立したプロセスに委任し、カーネル内の他の「プロセス」と並行して実行します。カーネル スレッドは、カーネル デーモンと呼ばれることがよくあります。現在のカーネルでは、カーネル スレッドは次の作業を担当します。

  • 変更されたメモリ ページとページ ソース ブロック デバイスを定期的に同期します。
  • ファイル システムのトランザクション ログを実装します。

カーネル スレッドはカーネルによって作成されるため、カーネル スレッドはカーネル モードで実行され、カーネル仮想アドレス空間にのみアクセスでき、ユーザー空間にはアクセスできません。

Linux では、すべてのスレッドがプロセスとして実装されており、スレッド用に定義された個別のスケジューリング アルゴリズムやデータ構造はありません。プロセスは、それ自体がマルチスレッドであり、元のスレッドである 1 つのスレッドを含むものと同等です。スレッドはメイン スレッドと呼ばれ、複数のスレッドが集まってスレッド グループを形成します。

プロセスには独自のアドレス空間があるため、各プロセスには独自のページ テーブルがありますが、スレッドにはありません。メイン スレッドのアドレス空間とページ テーブルを他のスレッドとのみ共有できます

2. 3 つのデータ構造

各プロセスまたはスレッドは、struct thread_info、struct task_struct、およびカーネル スタックという 3 つの重要なデータ構造で構成されます。

thread_info オブジェクトは、プロセス/スレッドの基本情報を格納するオブジェクトであり、プロセス/スレッドのカーネル スタックとともに、カーネル空間のページ長の 2 倍の領域に格納されます。 thread_info 構造体はアドレス セグメントの最後に格納され、残りの領域はカーネル スタックとして使用されます。カーネルはバディ システムを使用してこのスペースを割り当てます。

Linux にはカーネルレベルのスレッドがありますか?
#
struct thread_info {

	int			preempt_count;	/* 0 => preemptable,  bug */

	struct task_struct	*task;		/* main task structure */
	__u32			cpu;		/* cpu */};
thread_info 構造体に struct task_struct *task があり、task は次の task_struct を指します。スレッドまたはプロセス オブジェクト、task_struct はタスク記述子とも呼ばれます:

struct task_struct {
	
	pid_t pid;
	
	pid_t tgid;

	void *stack;
	struct mm_struct *mm, *active_mm;
	/* filesystem information */
	struct fs_struct *fs;
	/* open file information */
	struct files_struct *files;};#define task_thread_info(task)	((struct thread_info *)(task)->stack)
  • stack:是指向进程或者线程的thread_info
  • mm:对象用来管理该进程/线程的页表以及虚拟内存区
  • active_mm:主要用于内核线程访问主内核页全局目录
  • pid:每个task_struct都会有一个不同的id,就是pid
  • tgid:线程组领头线程的PID,就是主线程的pid

linux系统上虚拟地址空间分为两个部分:供用户态程序访问的虚拟地址空间和供内核访问的内核空间。每当内核执行上下文切换时,虚拟地址空间的用户层部分都会切换,以便匹配运行的进程,内核空间的部分是不会切换的。

3.内核线程创建

在内核版本linux-3.x以后,内核线程的创建被延后执行,并且交给名为kthreadd 2号线程执行创建过程,但是kthreadd本身是怎么创建的呢?过程如下:

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
		(unsigned long)arg, NULL, NULL);
}

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

kthreadadd本身最终是通过do_fork实现的,do_fork通过传入不同的参数,可以分别用于创建用户态进程/线程,内核线程等。当kthreadadd被创建以后,内核线程的创建交给它实现。

内核线程的创建分为创建和启动两个部分,kthread_run作为统一的接口,可以同时实现,这两个功能:

#define kthread_run(threadfn, data, namefmt, ...)			   \
({									   \
	struct task_struct *__k						   \
		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
	if (!IS_ERR(__k))						   \
		wake_up_process(__k);					   \
	__k;								   \
})

#define kthread_create(threadfn, data, namefmt, arg...) \
	kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)


struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
					   void *data, int node,
					   const char namefmt[],
					   ...)
{
	DECLARE_COMPLETION_ONSTACK(done);
	struct task_struct *task;

	/*分配kthread_create_info空间*/
	struct kthread_create_info *create = kmalloc(sizeof(*create),
						     GFP_KERNEL);

	if (!create)
		return ERR_PTR(-ENOMEM);
	create->threadfn = threadfn;
	create->data = data;
	create->node = node;
	create->done = &done;

	/*加入到kthread_creta_list列表中,等待ktherad_add中断线程去创建改线程*/
	spin_lock(&kthread_create_lock);
	list_add_tail(&create->list, &kthread_create_list);
	spin_unlock(&kthread_create_lock);

	wake_up_process(kthreadd_task);
	/*
	* Wait for completion in killable state, for I might be chosen by
	* the OOM killer while kthreadd is trying to allocate memory for
	* new kernel thread.
	*/
	if (unlikely(wait_for_completion_killable(&done))) {
		/*
		* If I was SIGKILLed before kthreadd (or new kernel thread)
		* calls complete(), leave the cleanup of this structure to
		* that thread.
		*/
		if (xchg(&create->done, NULL))
			return ERR_PTR(-EINTR);
		/*
		* kthreadd (or new kernel thread) will call complete()
		* shortly.
		*/
		wait_for_completion(&done);
	}
	task = create->result;
	.
	.
	.
	kfree(create);
	return task;
}

kthread_create_on_node函数中:

  • 首先利用kmalloc分配kthread_create_info变量create,利用函数参数初始化create
  • 将create加入kthread_create_list链表中,然后唤醒kthreadd内核线程创建当前线程
  • 唤醒kthreadd后,利用completion等待内核线程创建完成,completion完成后,释放create空间

下面来看下kthreadd的处理过程:

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);
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	
	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *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(;;)一直驻留在内存中运行:主要过程如下:

  • 检查kthread_create_list为空时,kthreadd让出cpu的执行权
  • kthread_create_list不为空时,利用while循环遍历kthread_create_list链表
  • 每取下一个链表节点后调用create_kthread,创建内核线程
static void create_kthread(struct kthread_create_info *create)
{
	int pid;


	/* We want our own signal handler (we take no signals by default). */
	pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
	if (pid done, NULL);

		if (!done) {
			kfree(create);
			return;
		}
		create->result = ERR_PTR(pid);
		complete(done);
	}
}

可以看到内核线程的创建最终还是和kthreadd一样,调用kernel_thread实现。

static int kthread(void *_create)
{
	.
	.
	.
	.
	/* 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 */
	__set_current_state(TASK_UNINTERRUPTIBLE);
	create->result = current;
	complete(done);
	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);
}

kthread以struct kthread_create_info 类型的create为参数,create中带有创建内核线程的回调函数,以及函数的参数。kthread中,完成completion信号量的处理,然后schedule让出cpu的执行权,等待下次返回 时,执行回调函数threadfn(data)。

4.内核线程的退出

线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。

int kthread_stop(struct task_struct *k)
{
	struct kthread *kthread;
	int ret;

	trace_sched_kthread_stop(k);

	get_task_struct(k);
	kthread = to_live_kthread(k);
	if (kthread) {
		set_bit(KTHREAD_SHOULD_STOP, &kthread->flags);
		__kthread_unpark(k, kthread);
		wake_up_process(k);
		wait_for_completion(&kthread->exited);
	}
	ret = k->exit_code;
	put_task_struct(k);

	trace_sched_kthread_stop_ret(ret);
	return ret;
}

如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止。在执行kthread_stop的时候,目标线程必须没有退出,否则会Oops。所以在创建thread_func时,可以采用以下形式:

thread_func()
{
    // do your work here
    // wait to exit
    while(!thread_could_stop())
    {
           wait();
    }
}

exit_code()
{
     kthread_stop(_task);   //发信号给task,通知其可以退出了
}

如果线程中在等待某个条件满足才能继续运行,所以只有满足了条件以后,才能调用kthread_stop杀掉内核线程。

5.内核线程使用

#include "test_kthread.h"
#include <linux>
#include <linux>

#include <linux>
#include <linux>
#include <linux>

static struct task_struct *test_thread = NULL;

unsigned int time_conut = 5;

int  test_thread_fun(void *data)
{
	int times = 0;
	while(!kthread_should_stop())
	{
		printk("\n   printk %u\r\n", times);
		times++;
		msleep_interruptible(time_conut*1000);
	}

	printk("\n   test_thread_fun exit success\r\n\n");

	return 0;
}


void register_test_thread(void)
{

	test_thread = kthread_run(test_thread_fun , NULL, "test_kthread" );
	
    if (IS_ERR(test_thread)){ 
        printk(KERN_INFO "create test_thread failed!\n"); 
    }  
    else { 
        printk(KERN_INFO "create test_thread ok!\n");  
    } 
	
}
static ssize_t kthread_debug_start(struct device *dev, struct device_attribute *attr, char *buf)
{
	register_test_thread();
	
	return 0;	
}


static ssize_t kthread_debug_stop(struct device *dev, struct device_attribute *attr, char *buf)
{
	kthread_stop(test_thread);
	
	return 0;	
}


static DEVICE_ATTR(kthread_start,  S_IRUSR,  kthread_debug_start,NULL);
static DEVICE_ATTR(kthread_stop, S_IRUSR,    kthread_debug_stop,NULL);

struct attribute * kthread_group_info_attrs[] = 
{
	&dev_attr_kthread_start.attr,
	&dev_attr_kthread_stop.attr,
	
	NULL,
};

struct attribute_group kthread_group = 
{
	.name = "kthread",
	.attrs = kthread_group_info_attrs,
};</linux></linux></linux></linux></linux>

相关推荐:《Linux视频教程

以上がLinux にはカーネルレベルのスレッドがありますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。