1. 头文件
#include <linux/sched.h> //wake_up_process()
#include <linux/kthread.h> //kthread_create()、kthread_run()
#include <err.h> //IS_ERR()、PTR_ERR()
2. 实现
2.1创建线程
在模块初始化时,可以进行线程的创建。使用下面的函数和宏定义:
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[], ...);
#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; \
})
例如:
static struct task_struct *test_task;
static int test_init_module(void)
{
int err;
test_task = kthread_create(test_thread, NULL, "test_task");
if(IS_ERR(test_task)){
printk("Unable to start kernel thread.\n");
err = PTR_ERR(test_task);
test_task = NULL;
return err;
}
wake_up_process(test_task);
return 0;
}
module_init(test_init_module);
2.2线程函数
在线程函数里,完成所需的业务逻辑工作。主要框架如下所示:
int threadfunc(void *data){
…
while(1){
set_current_state(TASK_UNINTERRUPTIBLE);
if(kthread_should_stop()) break;
if(){//条件为真
//进行业务处理
}
else{//条件为假
//让出CPU运行其他线程,并在指定的时间内重新被调度
schedule_timeout(HZ);
}
}
…
return 0;
}
2.3结束线程
在模块卸载时,可以结束线程的运行。使用下面的函数:
int kthread_stop(struct task_struct *k);
例如:
static void test_cleanup_module(void)
{
if(test_task){
kthread_stop(test_task);
test_task = NULL;
}
}
module_exit(test_cleanup_module);
3. 注意事项
(1) 在调用kthread_stop函数时,线程函数不能已经运行结束。否则,kthread_stop函数会一直进行等待。
(2) 线程函数必须能让出CPU,以便能运行其他线程。同时线程函数也必须能重新被调度运行。在例子程序中,这是通过schedule_timeout()函数完成的。
4.性能测试
可以使用top命令来查看线程(包括内核线程)的CPU利用率。命令如下:
top –p 线程号
可以使用下面命令来查找线程号:
ps aux|grep 线程名
注:线程名由kthread_create函数的第三个参数指定。
LINUX是一个多任务多进程的操作系统,在LINUX中进程和线程其实是没有太多区别的。只是多个线程可以同时访问同一块内存区域叫做共享内存。
那如何判定一个进程的优先级级别哪?其实LINUX有自己的一套算法。内核代码写的很清楚,意思就是根据进程的平均休眠时间来确定LINUX进程的优先级别,LINUX认为I/O型操作需要的睡眠时间长,需要相应及时,比如我通过键盘打字需要很快的让字显示到屏幕上,这就是一次I/O,而且需要及时,这样的进程往往优先级比较高。而CPU密集型的操作,优先级低,因为这种操作往往经历的时间叫长,所以我们可能时效性要求不高,所以它的资源经常被抢占。
如果查看进程的优先级?使用TOP就可以看到 NICE 一列就是 -20到19 越低优先级越高(nice值)、用户线程0-99、数值越高优先级越高。
进程在kernel中是被放在任务队列(task list)中的,task list 是个双向循环链表。链表中的每一项都是task_struct类型,即进程描述符。定义在<include/linux/sched.h>中,包含着一个具体进程的所有信息。
进程一直处于下面五种状态之一:
l TASK_RUNNING//运行状态
l TASK_INTERRUPTIBLE//可中断状态
l TASK_UNINTRRUPTIBLE//不可中断状态
l TASK_ZOMBIE//僵死
l TASK_STOPPED//停止
进程和线程的概念我就不讲了。总之,你记着:内核调度的对象是线程,而不是进程。linux系统中的线程很特别,它对线程和进程并不做特别区分。进程的另外一个名字叫任务(task).我和作者一样,习惯了把用户空间运行的程序叫做进程,把内核中运行的程序叫做任务。
内核把进程存放在叫做任务队列(task list)的双向循环链表中,链表中的每一项都是类型为task_struct,名称叫做进程描述符(process descriptor)的结构,该结构定义在include/linux/sched.h文件中,它包含了一个具体进程的所有信息。
现在我关心的问题是:当内核需要调整某个进程的状态时,该怎么做?这时最好使用set_task_state(task, state)函数,该函数将指定的进程设置为指定的状态,必要的时候,它会设置内存屏蔽来强制其他处理器作重新排序。(一般只有在SMP系统中有此必要)否则,它等价于:task->state = state; 另外set_current_state(state)和set_task_state(current, state)含义是等价的。
然而,有一件令人奇怪的事情,在linux中,并没有线程这个概念,linux中所有的线程都当作进程来处理,换句话说就是在内核中并没有什么特殊的结构和算法来表示线程。那么,说了这多,到底在linux中啥是线程,我们说在linux中,线程仅仅是一个使用共享资源的进程。每个线程都拥有一个隶属于自己的task_struct.所以说线程本质上还是进程,只不过该进程可以和其他一些进程共享某些资源信息。
内核线程和用户级线程的区别在于内核线程没有独立的地址空间(实际上它的mm指针被设置为NULL).它也可以被调度也可以被抢占。内核线程也只能由其他内核线程创建。
一个进程终结时必须释放它所占用的资源并把这一消息告诉其父进程。进程终止的方式有很多种,进程的析构发生在它调用exit()之后,即可能显示地调用这个系统调用,也可能隐式地从某个程序的主函数返回。
父进程创建子进程,然后子进程退出处释放占用的资源并告诉父进程自己的PID以及退出状态。问题就出在这里,子进程一定能保证在父进程前边退出么,这是没办法保证的,所以必须要有机制来保证子进程在这种情况下能找到一个新的父进程。否则的话,这些成为孤儿的进程就会在退出时永远处于僵死状态,白白的耗费内存。解决这个问题的办法,就是给子进程在当前线程组内找一个线程作为父亲,如果这样也不行(运气太背了,不是)。在do_exit()会调用notify_present(),该函数会通过forget_original_parent来执行寻父过程,
1,sched.c 是内核中有关进程调度管理的程序,其中有关调度的基本函数(sleep_on() , wakeup() ,schedule() 函数等) ,其中比较重要的一个函数是schedule()函数,
该函数负责选择系统中,下一个将要运行的进程,它首先对所有的任务进行选择,唤醒任何一个已经得到信号的任务。
具体方法是针对任务数组中的每个任务,检查其报警定时值alarm。如果任务的alarm时间已经过期(alarm < jiffies), 则在它的信号位图中设置SIGALRM信号,
然后清alarm的值,jiffies是系统是从开机开始算起的滴答数,如果进程的信号位图中除被阻塞的的信号外还有其他的信号,并且读进程处于可中断睡眠状态,则置进程为就绪状态。
随后是调度函数的核心处理部分,这部分代码根据进程的时间片和优先权调度机制,来选择随后要执行的任务。它首先循环检查任务数组中的所有任务,根据每个就绪态任务剩余执行时间的值counter,来选取该值最大的一个任务,并利用switch_to()函数切换到该任务。若所有就绪态任务该值都为0,表示此刻所有任务的时间片都
已经运行完毕,于是就根据任务的优先权值priority,重置每个任务的运行时间片值counter,再重新执行循环检查所有任务的执行时间片值。
sleep_on()函数的主要功能是当一个进程所请求的资源正忙或不在内存中时暂时切换出去,放在等待队列中等待一段时间,当切换回来之后再继续运行,放入等待队列的方式利用了函数中的tmp指针作为各个正在等待任务的联系。
以下是 内核模块中的 sched.c函数代码
结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到。
--摘自Posix多线程编程学习笔记(二)—线程属性(2)--转自湖光倒影