很多地方都有list_entry函数使用,http://blog.csdn.net/hs794502825/article/details/10364977
但是分析着很复杂,给个例子就很清楚了,
typedef struct xxx
{
......//其他值
type1 member;
......//其他值
}type;
定义变量:
type a;
type *b;
tpye1*ptr;
执行:
ptr=&(a.member);
b=list_entry(ptr,type,member);
这个操作可以使b指向a,得到了a的地址,就是从结构体内部数据入手,得到结构体自身指针。以中间类型为准。
这是使用结论,链接中有源代码分析为什么会这样的。
详细分析。 https://blog.csdn.net/wangliang888888/article/details/51213751
list_head从字面上理解,好像是头结点的意思。但从这里的代码来看却是普通结点的结构体,在后面的代码中将list_head当成普通的结点来处理。
1、定义: 初始化真正的头结点,而此类链表是链表自身存在于一个数据结构体中,
struct kool_list{ int to; struct list_head list; int from; };
struct kool_list mylist;
2、那么这种类型的,初始化是有两种方法:
INIT_LIST_HEAD(&mylist.list);或者
LIST_HEAD(mylist);
上述相当于struct list_head name = {.next=&name, .prev=&name} 此时的name是mylist.list
3、注意点 但是注意虽然自己定义了一个数据结构,但是初始化的时候,是初始化的自己定义的数据结构中的list这个部分,当然,使用第二种就不用这么细致了。
4、插入(注意使用链表是因为只用记忆一个头结点,其他的用链表指针指向即可,不用专门记忆,想知道任何节点内容,只能遍历!!!): 而剩下的插入,list_add(struct list_head *new, struct list_head *head)==__list_add(new, head, head->next);是将new插入head和head之后的节点之间。当只有一个head存在,,那么就是将new插在head之后。此时看到并不是说一定插在头结点后面,而是新节点插在第二个变量和第三个变量之间。
此处仍然要注意,因为加入的是参数是链表地址,并不是自己的结构体地址,而且第二个参数是自己的头结构体内部的所对应指针。如果想做到交换两个节点位置,这个与直接插入指定位置还是不相同的,,
5、static inline void list_add_tail(struct list_head *new, struct list_head *head)==__list_add(new, head->prev, head);这个明显能看出来是往前 插入。。
list_add和list_add_tail虽然原型一样,但调用底层函数__list_add时传递了不同的参数,从而实现了在head指向节点之前或之后添加新的对象
6、链表删除,
仅仅是把节点从链表中断开,但是节点自身空间本身是用户设定的,还需用户专门释放。
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
} 这个的意思是第一个参数是要删除节点的前一个节点,后面参数是要删除节点的后一个节点。
真正的删除为:
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
删除entry所指的结点,同时将entry所指向的结点指针域封死。可以看到该函数对上面函数进行调用。而且封死的意义是与下面函数对应的,static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
删除entry所指向的结点,同时调用LIST_INIT_HEAD()把被删除节点为作为链表头构建一个新的空双循环链表。因为这个是对切除下来的重新给构建了一个新的空双循环链表。
7、遍历的时候,一般都是从链表位置开始然后从链表位置找到自身的结构体指针,因为最终是想得到结构体,而不是链表的。
list_for_each(pos, &mylist.list){//pos是每个链表节点的位置
tmp=list_entry(pos, struct kool_list, list)// 此处是pos这个从list_for_each中取到的每个链表自身位置,然后第二个是说清楚链表节点所在的容器,也就是结构体本身的定义,而list是结构体的链表元素的成员名称,不是成员类型名称,类型是确定的struct list_head,就是list。所以三个分别是:链表值(从上list_for_each得到)、容器定义、链表成员自身在容器中的名字。
list_for_each_prev()这个是反向遍历方法。
8、还有个操作是移动操作。移动到某个位置,但是链表问题就是你只能确定一个头结构体指针,不能确定其他的节点地址,要想知道其他节点地址,只能像上面那样,遍历来找。
二、注意,主要是等待队列为什么睡眠函数里面放的是队列,但是睡眠的却是进程的原因,
注意等待队列,同样是队列,同样是双向链接表!!所以定义的
- struct __wait_queue_head {
- spinlock_t lock; /*因为等待队列可以在中断时随时修改,因此设置一个自旋锁保证一致性*/
- struct list_head task_list;
- };
这个是有一个list_head的,
- struct __wait_queue {
- unsigned int flags; /*指明等待的进程是互斥进程还是非互斥进程*/
- #define WQ_FLAG_EXCLUSIVE 0x01
- void *private; /*指向任务的task_struct*/
- wait_queue_func_t func;
- struct list_head task_list;
- };
这个同样有个list_head的,所以像双向链表一样,将wait_queue内部的list_head给链接到wait_queue_head上即可。
然后想将其加入等待队列的方法是:
首先wait_event参数是什么,以及实例分析:其实使用wait_queue这些接口,这个宏已经抽象地将调用wait_queue的所在进程给挂起来了。就是你随便定义一个等待队列头和等待队列,那么很简单,哪个进程使用就是挂哪个进程,而bdbm中是llm_那个处理线程的使用,所以调用wait_event就让这个线程等待了。使用add_wait_queue (&k->wq, k->wait);是等待,
schedule (); /* go to sleep */是睡眠,和wake_up_interruptible (&k->wq);是唤醒,都可以看到是只与调用这些函数的相关进程有关,你根本看不到内部是怎么根据wq和wait来处理相应进程的。
在bdbm中为什么
void bdbm_thread_schedule_setup (bdbm_thread_t* k)
{
add_wait_queue (&k->wq, k->wait);
set_current_state (TASK_INTERRUPTIBLE);
}
这个传进来
typedef struct {
/* thread management */
bdbm_mutex_t thread_done;
wait_queue_head_t wq;
wait_queue_t* wait;
struct task_struct* thread;
/* user management */
void* user_data;
int (*user_threadfn)(void *data);
} bdbm_thread_t;这个格式的wait添加到wq中就将真实的int __llm_mq_thread (void* arg)给停止了???这个是什么原因??
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char *namefmt, ...)将线程函数名称放在第一个参数,而第二是是参数,第三个是在系统中ps -e时候显示的名字。
int bdbm_thread_run (bdbm_thread_t* k)
{
/* wake up thread! */
wake_up_process (k->thread);
return 0;
}
这个是调用,bdbm_thread_create (
__llm_mq_thread, bdi, "__llm_mq_thread"))后直接再调用bdbm_thread_run 执行的。
与实际的kthread_create(int (*threadfn)(void *data),void *data,const char *namefmt, ...)线程创建后,不会马上运行,而是需要将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。只是这个步骤是产生线程的,但是还是没有说清楚怎么停止线程的???(就是wq,wait这两个值被哪个进程调用了就是停止哪个进程的。毫无解释办法,就是这样的,,,完全断层的解释。。。。深层次原理就是使用了双向链表。。但是怎么根据wait, 找到调用的进程然后停止进程,不清楚。。bdbm就是很复杂的实现了生成一个线程。但是关于线程等待确实linux内核将其太简化了。。)
定义运行的时候,k->user_threadfn = user_threadfn;
k->user_data = (void*)user_data;
k->wait = NULL;
这个将实际的__llm_mq_thread这个函数当成user_threadfn传到k中,然后将整个bdi当成user_data传进k中,然后对k的结构体内waitqueue进行初始化,然后k的thread,就是task_struct。而对应的__llm_mq_thread这个函数核心在实际的kthread_create (
bdbm_thread_fn, (void*)k, name)中被调用,而bdbm_thread_fn函数里面就有个执行k->user_threadfn的,就是将核心__llm_mq_thread给当成线程函数运行了,(绕来绕去就是将自己所有的东西封装在k中,再执行,还是执行的封装进去的原始值。)
https://blog.csdn.net/lizuobin2/article/details/51785812 这个链接讲的特别好。是拿着源码在讲,很好。
使用等待队列实现进程的阻塞
等待队列可以看做保存进程的容器,阻塞时候将进程保存进去,唤醒的时候从等待队列中取出。
分别有三种操作函数,定义等待队列,+初始化等待队列,=定义并初始化队列。
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
=DECLARE_WAIT_QUEUE_HEAD ( my_queue );
睡眠、唤醒两个过程。
简单睡眠:
wait_event(wait_queue_head_t* queue, condition);
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
上述是等待条件满足cpu将其来睡眠的,而wake_up函数
当编程者认为需要唤醒进程的时候,使用wake_up函数唤醒,但是其实即使用户唤醒了,还要判断condition是否满足,满足了才真正唤醒。
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
还有一种情况,纯粹的手动睡眠和唤醒,不让cpu控制,判断,完全人为控制,
使用定义绑定:
DECLARE_WAITQUEUE(name, tsk) 创建一个等待队列:
tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.//bdbm中确实是有
将队列头加入,移除等待队列中。 所以核心是带head_t的那个参数q.而wait是已经存在的这个队列。
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
设置进程状态
set_current_state(TASK_INTERRUPTIBLE);//一般这句话是与add_wait_queue链接在一起的,先加入队列然后设定可以打断模式
对进程的调度:
进程调度:
schedule() 或者 schedule_timeout()
只看一个wait_event()函数看内核是怎么实现的,因为参数中并没有进程本身。
将这个宏完全展开,有三层,结果如下:
- __wait_event(wq, condition);
- wait_queue_t __wait = { \
- .private = current, \
- .func = autoremove_wake_function, \
- .task_list = LIST_HEAD_INIT((__wait).task_list), \
- }
- for (;;) { \
- prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
- if (condition) \
- break; \
- schedule(); \
- } \
- finish_wait(&wq, &__wait);
唤醒过程本来不想看,但是bdbm抄了好多内核代码,只能看了,
- #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
- void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, void *key)
- {
- unsigned long flags;
- spin_lock_irqsave(&q->lock, flags);
- __wake_up_common(q, mode, nr_exclusive, 0, key);
- spin_unlock_irqrestore(&q->lock, flags);
- }
- static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, int sync, void *key)
- {
- struct list_head *tmp, *next;
- list_for_each_safe(tmp, next, &q->task_list) {
- wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
- unsigned flags = curr->flags;
- if (curr->func(curr, mode, sync, key) &&
- (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
- break;
- }
- }
- int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,
- void *key)
- {
- return try_to_wake_up(curr->private, mode, sync);
- }
唤醒后还要调度,同样是在:prepare_to_wait()判断条件为真就执行,如下,
- for (;;) { \
- prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
- if (condition) \
- break; \
- schedule(); \
- }
- DECLARE_WAITQUEUE(name, tsk)
- tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.
- #define DECLARE_WAITQUEUE(name, tsk) \
- wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
- #define __WAITQUEUE_INITIALIZER(name, tsk) { \
- .private = tsk, \
- .func = default_wake_function, \
- .task_list = { NULL, NULL } }
- void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
- void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
- {
- unsigned long flags;
- wait->flags &= ~WQ_FLAG_EXCLUSIVE;
- spin_lock_irqsave(&q->lock, flags);
- __add_wait_queue(q, wait);
- spin_unlock_irqrestore(&q->lock, flags);
- }
- set_current_state(TASK_INTERRUPTIBLE);
- schedule();
1、创建等待队列、等待队列头
2、将等待队列头加入到等待队列中去
3、设置当前进程的进程状态
4、进程调度~
上述注意对于add_wait_queue进行了分析,bdbm好像是摘抄了spin_lock_irqsave使用了。
本文深入探讨了Linux系统中结构体list的使用,包括list_entry函数如何从结构体内部数据获取结构体指针。此外,文章详细讲解了list_head的初始化、插入、删除操作,以及list_add和list_add_tail的区别。接着,文章转向等待队列的使用,阐述了等待队列头的结构、等待队列的添加、删除和唤醒过程,以及它们在进程阻塞和调度中的作用。通过实例代码和分析,帮助读者理解Linux内核中这些数据结构的实现细节。

2842

被折叠的 条评论
为什么被折叠?



