Linux にはカーネル レベルのスレッドがあり、Linux はカーネル レベルのマルチスレッドをサポートします。 Linux カーネルはサービス プロセス (ソフトウェアおよびハードウェア リソースの管理、ユーザー プロセスのさまざまなプロセスへの応答) と見なすことができ、カーネルは複数の実行ストリームを並行して必要とし、ブロッキングの可能性を防ぐためにマルチスレッドをサポートします。カーネル スレッドは、特定の処理に使用できるカーネルのクローンです。カーネルは、カーネル スレッドのスケジュールを担当します。1 つのカーネル スレッドがブロックされても、他のカーネル スレッドには影響しません。
#このチュートリアルの動作環境: 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 構造体はアドレス セグメントの最後に格納され、残りの領域はカーネル スタックとして使用されます。カーネルはバディ システムを使用してこのスペースを割り当てます。
#struct thread_info { int preempt_count; /* 0 => preemptable, bug */ struct task_struct *task; /* main task structure */ __u32 cpu; /* cpu */};
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)
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函数中:
下面来看下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(;;)一直驻留在内存中运行:主要过程如下:
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)。
线程一旦启动起来后,会一直运行,除非该线程主动调用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杀掉内核线程。
#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 サイトの他の関連記事を参照してください。