linux的结构体list使用。list_entry使用。链表。 和等待队列基本使用方法

本文深入探讨了Linux系统中结构体list的使用,包括list_entry函数如何从结构体内部数据获取结构体指针。此外,文章详细讲解了list_head的初始化、插入、删除操作,以及list_add和list_add_tail的区别。接着,文章转向等待队列的使用,阐述了等待队列头的结构、等待队列的添加、删除和唤醒过程,以及它们在进程阻塞和调度中的作用。通过实例代码和分析,帮助读者理解Linux内核中这些数据结构的实现细节。

很多地方都有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()这个是反向遍历方法。

 * list_for_each_safe     -      iterate over a list safe against removal of list entry
  */
#define list_for_each_safe(pos, n, head) \
       for (pos = (head)->next, n = pos->next; pos != (head); \
              pos = n, n = pos->next)
在for循环中n暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。也就是说你 可以遍历完当前节点后将其删除同时可以 接着访问下一个节点,遍历完毕后就只剩下一个头节点。这就叫safe。



8、还有个操作是移动操作。移动到某个位置,但是链表问题就是你只能确定一个头结构体指针,不能确定其他的节点地址,要想知道其他节点地址,只能像上面那样,遍历来找。



二、注意,主要是等待队列为什么睡眠函数里面放的是队列,但是睡眠的却是进程的原因

注意等待队列,同样是队列,同样是双向链接表!!所以定义的

  1. struct __wait_queue_head {
  2.     spinlock_t lock;      /*因为等待队列可以在中断时随时修改,因此设置一个自旋锁保证一致性*/
  3.     struct list_head task_list;
  4. };

这个是有一个list_head的,

  1. struct __wait_queue {
  2.     unsigned int flags; /*指明等待的进程是互斥进程还是非互斥进程*/
  3. #define WQ_FLAG_EXCLUSIVE    0x01
  4.     void *private; /*指向任务的task_struct*/
  5.     wait_queue_func_t func;
  6.     struct list_head task_list;
  7. };

这个同样有个list_head的,所以像双向链表一样,将wait_queue内部的list_head给链接到wait_queue_head上即可。

然后想将其加入等待队列的方法是:

1)为使当前进程在一个等待队列中睡眠,需要调用 wait_event(或某个等价函数),此后,进程进入睡眠,将控制权交给调度器。以块设备为例,当内核向块设备发出请求后,因为数据传输不会立即发生,因此进程睡眠
(2)相对应的,是当数据到达后,必须调用wake_up函数(或某个等价函数)来唤醒等待队列中睡眠的进程

首先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()函数看内核是怎么实现的,因为参数中并没有进程本身。

将这个宏完全展开,有三层,结果如下:

  1. __wait_event(wq, condition);  
  2. wait_queue_t __wait = {                     \  
  3.         .private    = current,              \  
  4.         .func       = autoremove_wake_function,     \  
  5.         .task_list  = LIST_HEAD_INIT((__wait).task_list),   \  
  6.     }  
  7.     for (;;) {                          \  
  8.         prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  
  9.         if (condition)                      \  
  10.             break;                      \  
  11.         schedule();                     \  
  12.     }                               \  
  13.     finish_wait(&wq, &__wait);  
额外多造出来的wait_queue_t __wait这个等待队列结构体, 将进程与等待队列连接起来,其实这个结构体__wait甚至与自己传入的参数wq都没有什么关系,核心就是识别当前的curret进程,然后调用prepare_to_wait将wq这个自己定义的等待队列头加入等待队列,然后设置当前状态为TASK_UNINTERRUPTIBLE。当然接下来,如果condition为 假,那么就睡眠,说明条件符合是不睡眠的,并不是说条件满足睡眠,这点注意。当条件满足时候,就进入schedule.此时陷入停滞。当条件为真时候,跳出,那么最后一句finish_wait就是恢复之前的工作,然后继续运行吧,不要睡了。





唤醒过程本来不想看,但是bdbm抄了好多内核代码,只能看了,

  1. #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)  
  1. void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,  
  2.             int nr_exclusive, void *key)  
  3. {  
  4.     unsigned long flags;  
  5.   
  6.     spin_lock_irqsave(&q->lock, flags);  
  7.     __wake_up_common(q, mode, nr_exclusive, 0, key);  
  8.     spin_unlock_irqrestore(&q->lock, flags);  
  9. }  
  1. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  2.                  int nr_exclusive, int sync, void *key)  
  3. {  
  4.     struct list_head *tmp, *next;  
  5.   
  6.     list_for_each_safe(tmp, next, &q->task_list) {  
  7.         wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);  
  8.         unsigned flags = curr->flags;  
  9.   
  10.         if (curr->func(curr, mode, sync, key) &&  
  11.                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
  12.             break;  
  13.     }  
  14. }  
[cpp]  view plain  copy
  1. int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,  
  2.               void *key)  
  3. {  
  4.     return try_to_wake_up(curr->private, mode, sync);  
  5. }  
   最终调用到 default_wake_function 来唤醒 等待队列里 private 里指定的那个进程。然后,移除将等待队列头移除等待队列。try_to_wake_up ,会将 要唤醒进程的 进程状态设置为 TASK_RUNNING ,然后放到 “运行队列”中。

唤醒后还要调度,同样是在:prepare_to_wait()判断条件为真就执行,如下,

[cpp]  view plain  copy
  1. for (;;) {                          \  
  2.     prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  
  3.     if (condition)                      \  
  4.         break;                      \  
  5.     schedule();                     \  
  6. }  
手动休眠就不用判断什么 condition 了。
[cpp]  view plain  copy
  1. DECLARE_WAITQUEUE(name, tsk)  
  2. tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.  
  3.   
  4. #define DECLARE_WAITQUEUE(name, tsk)                    \  
  5.     wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)  
  6. #define __WAITQUEUE_INITIALIZER(name, tsk) {                \  
  7.     .private    = tsk,                        \  
  8.     .func        = default_wake_function,            \  
  9.     .task_list    = { NULL, NULL } }  
  10.       
  11. void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);  
  12. void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);  
  13. void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);  
  14.   
  15. void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)  
  16. {  
  17.     unsigned long flags;  
  18.   
  19.     wait->flags &= ~WQ_FLAG_EXCLUSIVE;  
  20.     spin_lock_irqsave(&q->lock, flags);  
  21.     __add_wait_queue(q, wait);  
  22.     spin_unlock_irqrestore(&q->lock, flags);  
  23. }  
  24.   
  25. set_current_state(TASK_INTERRUPTIBLE);    
  26. schedule();  
   简单明了:

    1、创建等待队列、等待队列头

    2、将等待队列头加入到等待队列中去

    3、设置当前进程的进程状态

    4、进程调度~
上述注意对于add_wait_queue进行了分析,bdbm好像是摘抄了spin_lock_irqsave使用了。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值