日志标签 ‘内核线程’

内核任务

2012年5月31日

内核任务是指内核中执行的一切活动对象,每个内核任务都拥有一个独立的程序计数器、栈和一组寄存器。我们可以将Linux内核看作是不断对请求进行响应的服务器,这里的请求可能来自CPU上正在执行的进程,也可能是来自发出中断请求的外部设备。这个类比用来强调内核中各个任务之间并不是严格按照顺序执行的,而是采用交错执行的方式。本文简单说明内核中的任务分类,以及每种内核任务的特点。

内核线程

内核线程只运行在内核空间,它为内核完成一些周期性任务,比如用于执行工作队列的keventd线程,用于执行内存回收的kswapd线程以及用于将脏缓冲区中的内容刷新到磁盘上的pdflush线程等。内核线程与用户进程在某些方面比较类似,比如它们被内核中同一个进程调度器所调度、都通过do_fork()创建等。这主要是因为内核线程与进程都通过进程描述符task_struct来描述,我们可以将内核线程看作是运行在内核空间的进程。内核中使用thread来描述内核线程是因为它并不拥有用户空间,因此内核线程之间的切换是迅速的。虽然都使用了线程一词,但是应该和用户态的线程有所区分。

系统调用

用户态进程必须通过系统调用才能进入内核并获得内核提供的服务,比如访问硬件设备。系统调用是一种异常,它通过128号中断向量向内核发出一个明确的请求。系统一旦执行某个系统调用,就从用户态切换到内核态,接下来内核就代表发出系统调用的进程执行,执行完毕后又返回用户空间。由于系统调用是内核代表进程执行,因此它可以获得当前进程的信息,比如可以访问当前进程的描述符中的信息,而且系统调用的执行使用的是当前发出系统调用进程的时间片。

中断处理程序

根据Intel的说法,异步中断也被简称为中断,它是由硬件设备依照CPU时钟信号随机产生的。这类中断可以打断任何正在执行的内核任务。在响应一个特定中断的时候,内核会执行一个函数,该函数称为中断处理程序(Interrupt Handler)。如果一条中断线上共享了多个设备,那么每个设备将对应一个中断服务例程(Interrupt Server Routine,ISR)。当该某个中断发生时,内核会调用相应的中断处理程序。该中断处理程序首先在内核态堆栈中保存IRQ和寄存器的值,然后响应该中断,接着执行共享这个中断线上所有设备的中断服务例程,执行完毕后恢复之前被打断的内核任务执行现场。

异常处理程序

同步中断也被称为异常,它是由CPU控制单元产生的,只有在一条指令终止执行后CPU才会发出异常。内核为每一种异常提供了一个专门的异常处理程序。异常处理程序的执行过程与中断处理程序类似,它首先将大多数寄存器的值压入内核堆栈中,接着调用响应异常处理程序,最后从异常处理程序中返回并向产生异常的进程发出一个信号。

可延迟函数

中断随时可能发生,因此中断处理程序也就随时会被执行。为了能尽快恢复被中断的代码再次执行,中断处理程序必须快速运行完毕。但是,中断处理程序极有可能和硬件设备进行交互,它需要花一些时间和硬件进行数据交互,比如等待数据的到来,从外设拷贝数据到内存等。中断程序既要快速运行,又要完成大量工作,这两者显然存在矛盾。为了解决这个问题,内核将中断处理的过程分为上下两部分,上部分即中断处理程序,主要对中断请求进行快速响应;下半部分主要完成中断处理过程中对时间要求相对宽松的工作。下半部分包含三种机制:软中断(softirq,与上文中软件中断不同),tasklet以及工作队列。鉴于工作队列通过keventd内核线程来执行,因此将软中断和tasklet统称为可延迟函数。

进程在Linux内核中的角色扮演

2011年9月2日

在Linux内核中,内核将进程、线程和内核线程一视同仁,即内核使用唯一的数据结构task_struct来分别表示他们;内核使用相同的调度算法对这三者进行调度;并且内核也使用同一个函数do_fork()来分别创建这三种执行线程(thread of execution)。执行线程通常是指任何正在执行的代码实例,比如一个内核线程,一个中断处理程序或一个进入内核的进程。

这样处理无疑是简洁方便的,并且内核在统一处理这三者之余并没有失去他们本身所具有的特性。本文将结合进程、线程和内核线程的特性浅谈进程在内核中的角色扮演问题。

1.进程描述符task_struct的多角色扮演

上述三种执行线程在内核中都使用统一的数据结构task_struct来表示。task_struct结构即所谓的进程描述符,它包含了与一个进程相关的所有信息。进程描述符中不仅包含了许多描述进程属性的字段,而且还有一系列指向其他数据结构的指针。下面将简单介绍进程描述符中几个比较特殊的字段,它们分别指向代表进程所拥有的资源的数据结构。

mm字段:指向mm_struct结构的指针,该类型用来描述进程整个的虚拟地址空间。

fs字段:指向fs_struct结构的指针,该类型用来描述进程所在文件系统的根目录和当前进程所在的目录信息。

files字段:指向files_struct结构的指针,该类型用来描述当前进程所打开文件的信息。

signal字段:指向signal_struct结构(信号描述符)的指针,该类型用来描述进程所能处理的信号。

对于普通进程来说,上述字段分别指向具体的数据结构以表示该进程所拥有的资源。

对应每个线程而言,内核通过轻量级进程与其进行关联。轻量级进程之所轻量,是因为它与其他进程共享上述所提及的进程资源。比如进程A创建了线程B,则B线程会在内核中对应一个轻量级进程。这个轻量级进程很自然的对应一个进程描述符,只不过B线程的进程描述符中的某些代表资源指针会和A进程中对应的字段指向同一个数据结构,这样就实现了多线程之间的资源共享。

由于内核线程只运行在内核态,并且只能由其他内核线程创建,所以内核线程并不需要和普通进程那样的独立地址空间。因此内核线程的进程描述符中的mm指针即为NULL。内核线程是否共享父内核线程的某些资源,则通过向内核线程创建函数kernel_thread()传递参数来决定。

通过上面的分析可以发现,内核中使用统一的进程描述符来表示进程、线程和内核线程,根据他们不同的特性,其进程描述符中某些代表资源的字段的指向会有所不同,以实现扮演不同角色。

2. do_fork()的多角色扮演

进程、线程以及内核线程都有对应的创建函数,不过这三者所对应的创建函数最终在内核都是由do_fork()进行创建的,具体的调用关系图如下:

从图中可以看出,内核中创建进程的核心函数即为看do_fork(),该函数的原型如下:

long do_fork(unsigned long clone_flags,
               unsigned long stack_start,
               struct pt_regs *regs,
               unsigned long stack_size,
               int __user *parent_tidptr,
               int __user *child_tidptr)

该函数的参数个数是固定的,每个参数的功能如下:

clone_flags:代表进程各种特性的标志。低字节指定子进程结束时发送给父进程的信号代码,一般为SIGCHLD信号,剩余三个字节是若干个标志或运算的结果。

stack_start:子进程用户态堆栈的指针,该参数会被赋值给子进程的esp寄存器。

regs:指向通用寄存器值的指针,当进程从用户态切换到内核态时通用寄存器中的值会被保存到内核态堆栈中。

stack_size:未被使用,默认值为0。

parent_tidptr:该子进程的父进程用户态变量的地址,仅当CLONE_PARENT_SETTID被设置时有效。

child_tidptr:该子进程用户态变量的地址,仅当CLONE_CHILD_SETTID被设置时有效。

既然进程、线程和内核线程在内核中都是通过do_fork()完成创建的,那么do_fork()是如何体现其功能的多样性?其实,clone_flags参数在这里起到了关键作用,通过选取不同的标志,从而保证了do_fork()函数实现多角色——创建进程、线程和内核线程——功能的实现。clone_flags参数可取的标志很多,下面只介绍几个与本文相关的标志。

CLONE_VIM:子进程共享父进程内存描述符和所有的页表。

CLONE_FS:子进程共享父进程所在文件系统的根目录和当前工作目录。

CLONE_FILES:子进程共享父进程打开的文件。

CLONE_SIGHAND:子进程共享父进程的信号处理程序、阻塞信号和挂起的信号。使用该标志必须同时设置CLONE_VM标志。

如果创建子进程时设置了上述标志,那么子进程会共享这些标志所代表的父进程资源。

2.1 进程的创建

在用户态程序中,可以通过fork()、vfork()和clone()三个接口函数创建进程,这三个函数在库中分别对应同名的系统调用。系统调用函数通过128号软中断进入内核后,会调用相应的系统调用服务例程。这三个函数对应的服务历程分别是sys_fork()、sys_vfork()和sys_clone()。

 int sys_fork(struct pt_regs *regs)
 {
         return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
 }

 int sys_vfork(struct pt_regs *regs)
 {
         return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0,
                        NULL, NULL);
 }

 long
 sys_clone(unsigned long clone_flags, unsigned long newsp,
           void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
 {
         if (!newsp)
                 newsp = regs->sp;
         return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
 }

通过上述系统调用服务例程的源码可以发现,三个服务历程内部都调用了do_fork(),只不过差别在于第一个参数所传的值不同。这也正好导致由这三个进程创建函数所创建的进程有不同的特性。下面对每种进程作以简单说明。

fork():由于do_fork()中clone_flags参数除了子进程结束时返回给父进程的SIGCHLD信号外并无其他特性标志,因此由fork()创建的进程不会共享父进程的任何资源。子进程会完全复制父进程的资源,也就是说父子进程相对独立。不过由于写时复制技术(Copy On Write,COW)的引入,子进程可以只读父进程的物理页,只有当两者之一去写某个物理页时,内核此时才会将这个页的内容拷贝到一个新的物理页,并把这个新的物理页分配给正在写的进程。

vfork():do_fork()中的clone_flags使用了CLONE_VFORK和CLONE_VM两个标志。CLONE_VFORK标志使得子进程先于父进程执行,父进程会阻塞到子进程结束或执行新的程序。CLONE_VM标志使得子进程共享父进程的内存地址空间(父进程的页表项除外)。在COW技术引入之前,vfork()适用子进程形成后立马执行execv()的情形。因此,vfork()现如今已经没有特别的使用之处,因为写实复制技术完全可以取代它创建进程时所带来的高效性。

clone():clone通常用于创建轻量级进程。通过传递不同的标志可以对父子进程之间数据的共享和复制作精确的控制,一般flags的取值为CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND。由上述标志可以看到,轻量级进程通常共享父进程的内存地址空间、父进程所在文件系统的根目录以及工作目录信息、父进程当前打开的文件以及父进程所拥有的信号处理函数。

2.2 线程的创建

每个线程在内核中对应一个轻量级进程,两者的关联是通过线程库完成的。因此通过pthread_create()创建的线程最终在内核中是通过clone()完成创建的,而clone()最终调用do_fork()。

2.3 内核线程的创建

一个新内核线程的创建是通过在现有的内核线程中使用kernel_thread()而创建的,其本质也是向do_fork()提供特定的flags标志而创建的。

 int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
 {
        /*some register operations*/
         return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
 }

从上面的组合的flag可以看出,新的内核线程至少会共享父内核线程的内存地址空间。这样做其实是为了避免赋值调用线程的页表,因为内核线程无论如何都不会访问用户地址空间。CLONE_UNTRACED标志保证内核线程不会被任何进程所跟踪,

3. 进程的调度

由于进程、线程和内核线程使用统一数据结构来表示,因此内核对这三者并不作区分,也不会为其中某一个设立单独的调度算法。内核将这三者一视同仁,进行统一的调度。

参考资料:

1. 深入理解Linux内核

2. Linux内核设计与实现

生产者-消费者模型(使用内核线程实现)

2010年10月25日

在IPC系列文章当中,利用信号量集实现的是用户态下生产者-消费者模型的模型。本文以内核模块的方式,通过创建内核线程来为大家演示内核态下的生产者消费者模型。本模型属于np-nc-nb,即多个生产者多个消费着多个缓冲区。

在加载函数中,完成一些初始化的工作,并分别创建了5个生产者线程和消费者线程。kernel_thread函数的第一个参数是所所创建线程要做的动作;通过第二个参数传递i变量。

static int __init np_nc_init(void)
{
	int i;

	printk("np_nc module is working..\n");
	in=out=0;
	cflag=0;
	init_MUTEX(&mutex);
	sema_init(&s1,BUF_NUM);
	sema_init(&s2,0);

	for(i=0;i< N;i++)
	{
		index[i]=i+1;
		kernel_thread(productor_thread,&(index[i]),CLONE_KERNEL);
		kernel_thread(consumer_thread,&(index[i]),CLONE_KERNEL);
	}

	return 0;
}

在生产者函数中,PNUM是每个生产者要生产货物的数量。语句buf[in]=i*100+(PNUM-p_num+1)即是产生货物的过程。

int productor_thread(void *p)
{
	int i=*(int *)p;
	int p_num=PNUM;

	while(p_num)
	{
		if((s1.count)<=0)
		{
			printk("[producer %d,%d]:I will be waiting for producting..\n",i,s1.count);
		}
		down(&s1);
	        down(&mutex);
          	buf[in]=i*100+(PNUM-p_num+1);
          	printk("[producer %d,%d]:I producted a goods \"%d\" to buf[%d]..\n",i,s1.count,buf[in],in);
            	in=(in+1)%BUF_NUM;
         	up(&mutex);
          	up(&s2);
		p_num--;
	}
	return 0;
}

在消费者函数中,通过一个全局变量CNUM来控制消费者总共的消费次数。

int consumer_thread(void *p)
{
	int i=*(int *)p;
	int goods;

	while(cflag!=CNUM)
	{
		if((s2.count)<=0)
		{
			printk("[consumer %d,%d]:I will be waiting for goods..\n",i,s2.count);
		}
	        down(&s2);
            	down(&mutex);
          	goods=buf[out];
           	printk("[consumer %d,%d]:I consumed a goods \"%d\" from buf[%d]..\n",i,s2.count,goods,(out%BUF_NUM));
           	out=(out+1)%BUF_NUM;
            	up(&mutex);
            	up(&s1);
		cflag++;
	}
	return 0;
}

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key