Verfügt Linux über Threads auf Kernel-Ebene?

青灯夜游
Freigeben: 2022-11-11 14:20:32
Original
2278 Leute haben es durchsucht

Linux verfügt über Threads auf Kernel-Ebene und Linux unterstützt Multithreading auf Kernel-Ebene. Der Linux-Kernel kann als Dienstprozess betrachtet werden (Verwaltung von Software- und Hardwareressourcen, Reaktion auf verschiedene Prozesse von Benutzerprozessen); der Kernel erfordert mehrere Ausführungsströme parallel und unterstützt Multithreading, um mögliche Blockierungen zu verhindern. Ein Kernel-Thread ist ein Klon des Kernels, der zur Verarbeitung einer bestimmten Sache verwendet werden kann. Der Kernel ist für die Planung von Kernel-Threads verantwortlich. Wenn ein Kernel-Thread blockiert ist, hat dies keine Auswirkungen.

Verfügt Linux über Threads auf Kernel-Ebene?

Die Betriebsumgebung dieses Tutorials: Linux7.3-System, Dell G3-Computer.

Threads werden normalerweise als verschiedene Ausführungsrouten von Code innerhalb eines Prozesses definiert. In Bezug auf die Implementierung gibt es zwei Arten von Threads: „Threads auf Benutzerebene“ und „Threads auf Kernelebene“.

Benutzer-Threads beziehen sich auf Threads, die in Benutzerprogrammen ohne Kernel-Unterstützung implementiert sind. Sie sind nicht vom Betriebssystemkern abhängig. Anwendungsprozesse nutzen die Thread-Bibliothek, um Funktionen zum Erstellen, Synchronisieren, Planen und Verwalten von Threads bereitzustellen . Diese Art von Thread kann sogar in Betriebssystemen wie DOS implementiert werden, die Planung von Threads muss jedoch vom Benutzerprogramm durchgeführt werden, was dem kooperativen Multitasking von Windows 3.x etwas ähnelt.

Der andere erfordert die Beteiligung des Kernels, der die Thread-Planung abschließt. Es basiert auf dem Betriebssystemkern und wird durch die internen Anforderungen des Kernels erstellt und zerstört. Beide Modelle haben ihre eigenen Vor- und Nachteile.

Benutzer-Threads erfordern keinen zusätzlichen Kernel-Overhead, und die Implementierung von Benutzermodus-Threads kann an die Anforderungen spezieller Anwendungen angepasst oder geändert werden. Wenn sich ein Thread jedoch aufgrund von E/A im Wartezustand befindet, wird dies für den gesamten Prozess der Fall sein geplant werden Wenn das Programm in den Wartezustand wechselt, haben andere Threads keine Chance zur Ausführung, während der Kernel-Thread keinen Einschränkungen unterliegt, was der Nutzung der Parallelität von Multiprozessoren förderlich ist, aber höhere Systemkosten verursacht .

Windows NT und OS/2 unterstützen Kernel-Threads. Linux unterstützt Multithreading auf Kernel-Ebene.

Threads auf Kernelebene in Linux

1. Kernel-Thread-Übersicht

Der Linux-Kernel kann als Serviceprozess betrachtet werden (Verwaltung von Software- und Hardwareressourcen, Reaktion auf verschiedene Prozesse von Benutzerprozessen)

Kernel benötigt mehrere parallele Ausführungsströme und um mögliche Blockierungen zu verhindern, wird Multithreading unterstützt.

Ein Kernel-Thread ist ein Klon des Kernels, der zur Verarbeitung einer bestimmten Sache verwendet werden kann. Der Kernel ist für die Planung von Kernel-Threads verantwortlich. Wenn ein Kernel-Thread blockiert ist, hat dies keine Auswirkungen.

Kernel-Threads sind Prozesse, die direkt vom Kernel selbst gestartet werden. Kernel-Threads delegieren Kernel-Funktionen tatsächlich zur Ausführung an unabhängige Prozesse, die parallel zu anderen „Prozessen“ im Kernel ausgeführt werden. Kernel-Threads werden oft als Kernel-Daemons bezeichnet. Im aktuellen Kernel ist der Kernel-Thread für die folgende Arbeit verantwortlich:

  • Synchronisieren Sie die geänderte Speicherseite regelmäßig mit dem Seitenquellblockgerät.
  • Implementieren Sie das Transaktionsprotokoll des Dateisystems.

Der Kernel-Thread wird von erstellt Kernel, also der Kernel-Thread Bei der Ausführung im Kernel-Modus können Sie nur auf den virtuellen Adressraum des Kernels und nicht auf den Benutzerbereich zugreifen.

In Linux sind alle Threads als Prozesse implementiert, und es gibt keinen separaten Planungsalgorithmus und keine separate Datenstruktur für Threads. Ein Prozess entspricht einem Thread, der selbst mehrere Threads enthält. Der ursprüngliche Thread wird als Hauptthread bezeichnet , und sie bilden zusammen eine Thread-Gruppe.

Der Prozess verfügt über einen eigenen Adressraum, sodass jeder Prozess über eine eigene Seitentabelle verfügt, der Thread jedoch nicht. Er kann nur den Adressraum und die Seitentabelle des Hauptthreads mit anderen Threads teilen.

2 Strukturen

Jeder Prozess oder Thread besteht aus drei wichtigen Datenstrukturen, nämlich struct thread_info, struct task_struct und Kernel Stack.

Das thread_info-Objekt speichert die grundlegenden Informationen des Prozesses/Threads. Es und der Kernel-Stack des Prozesses/Threads werden in einem Bereich gespeichert, der doppelt so lang ist wie die Seitenlänge im Kernel-Bereich. Die thread_info-Struktur wird am Ende des Adresssegments gespeichert und der verbleibende Speicherplatz wird als Kernel-Stack verwendet. Der Kernel verwendet das Buddy-System, um diesen Speicherplatz zuzuweisen.

Verfügt Linux über Threads auf Kernel-Ebene?
struct thread_info {

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

	struct task_struct	*task;		/* main task structure */
	__u32			cpu;		/* cpu */};
Nach dem Login kopieren

Es gibt eine Struktur task_struct *task, die auf das task_struct-Objekt des Threads oder Prozesses verweist:

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)
Nach dem Login kopieren
  • 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);
Nach dem Login kopieren

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;
}
Nach dem Login kopieren

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;
}
Nach dem Login kopieren

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);
	}
}
Nach dem Login kopieren

可以看到内核线程的创建最终还是和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);
}
Nach dem Login kopieren

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;
}
Nach dem Login kopieren

如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止。在执行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,通知其可以退出了
}
Nach dem Login kopieren

如果线程中在等待某个条件满足才能继续运行,所以只有满足了条件以后,才能调用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>
Nach dem Login kopieren

相关推荐:《Linux视频教程

Das obige ist der detaillierte Inhalt vonVerfügt Linux über Threads auf Kernel-Ebene?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!