Linux中的锁机制 —— osq lock

osq 数据结构

6  /*
7   * An MCS like lock especially tailored for optimistic spinning for sleeping
8   * lock implementations (mutex, rwsem, etc).
9   *
10   * Using a single mcs node per CPU is safe because sleeping locks should not be
11   * called from interrupt context and we have preemption disabled while
12   * spinning.
13   */

作为 MCS 锁的衍化,专门为 mutex, rwsem 等睡眠锁量身定制了可以乐观自旋的 osq 锁(optimistic spinning queue)。

9  struct optimistic_spin_node {
10  	struct optimistic_spin_node *next, *prev;-------------------next和prev指针可以组成一个双向链表。
11  	int locked; /* 1 if lock acquired */------------------------表示加锁状态。
12  	int cpu; /* encoded CPU # + 1 value */----------------------用于重新编码CPU编号,表示该node在那个CPU上。
13  };
14  
15  struct optimistic_spin_queue {
16  	/*
17  	 * Stores an encoded value of the CPU # of the tail node in the queue.
18  	 * If the queue is empty, then it's set to OSQ_UNLOCKED_VAL.
19  	 */
20  	atomic_t tail;
21  };

struct optimistic_spin_node数据结构会定义成 per-CPU 变量,即每个 CPU 有一个 node 结构。

static DEFINE_PER_CPU_SHARED_ALIGNED(struct optimistic_spin_node, osq_node);

osq 初始化

把队列 tail 设置为 OSQ_UNLOCKED_VAL,即 0。

23  #define OSQ_UNLOCKED_VAL (0)
24  
25  /* Init macro and function. */
26  #define OSQ_LOCK_UNLOCKED { ATOMIC_INIT(OSQ_UNLOCKED_VAL) }
27  
28  static inline void osq_lock_init(struct optimistic_spin_queue *lock)
29  {
30  	atomic_set(&lock->tail, OSQ_UNLOCKED_VAL);
31  }

加锁/解锁

90  bool osq_lock(struct optimistic_spin_queue *lock)
91  {
92  	struct optimistic_spin_node *node = this_cpu_ptr(&osq_node);-----------node 指向当前 CPU 的 struct optimistic_spin_node 节点。
93  	struct optimistic_spin_node *prev, *next;
94  	int curr = encode_cpu(smp_processor_id());-----------------------------表示当前 CPU 编号,0 表示没有 CPU,1 表示 CPU0,以此类推。
95  	int old;
96  
97  	node->locked = 0;
98  	node->next = NULL;
99  	node->cpu = curr;
100  
101  	/*
102  	 * We need both ACQUIRE (pairs with corresponding RELEASE in
103  	 * unlock() uncontended, or fastpath) and RELEASE (to publish
104  	 * the node fields we just initialised) semantics when updating
105  	 * the lock tail.
106  	 */
107  	old = atomic_xchg(&lock->tail, curr);-----------使用原子交换函数 atomic_xchg() 交换全局 lock->tail 和当前 CPU 号,如果 lock->tail 就只等于初始化 OSQ_UNLOCKED_VAL,说明没有人持锁,那么让 lock->tail 等于当前 CPU 标号表示成功持锁。(atomic_xchg则是将新值存入变量,并将变量的旧值返回)
108  	if (old == OSQ_UNLOCKED_VAL) -------------------如果 lock->tail 就只等于初始化 OSQ_UNLOCKED_VAL,说明没有人持锁
109  		return true;
110  
111  	prev = decode_cpu(old);-------------------------之前获取锁失败,prev 表示 old 指向的 CPU 所属节点的 struct optimistic_spin_node 数据结构。
112  	node->prev = prev;
113  
114  	/*
115  	 * osq_lock()			unqueue
116  	 *
117  	 * node->prev = prev		osq_wait_next()
118  	 * WMB				MB
119  	 * prev->next = node		next->prev = prev // unqueue-C
120  	 *
121  	 * Here 'node->prev' and 'next->prev' are the same variable and we need
122  	 * to ensure these stores happen in-order to avoid corrupting the list.
123  	 */
124  	smp_wmb();
125  
126  	WRITE_ONCE(prev->next, node);
127  
128  	/*
129  	 * Normally @prev is untouchable after the above store; because at that
130  	 * moment unlock can proceed and wipe the node element from stack.
131  	 *
132  	 * However, since our nodes are static per-cpu storage, we're
133  	 * guaranteed their existence -- this allows us to apply
134  	 * cmpxchg in an attempt to undo our queueing.
135  	 */
136  
137  	while (!READ_ONCE(node->locked)) {------------一直查询当前节点 node->locked 是否变成了1,因为前继节点 prev 释放锁时会把它的下一个节点中的 locked 成员置为1,然后才能成功释放锁。
138  		/*
139  		 * If we need to reschedule bail... so we can block.
140  		 * Use vcpu_is_preempted() to avoid waiting for a preempted
141  		 * lock holder:
142  		 */
143  		if (need_resched() || vcpu_is_preempted(node_cpu(node->prev)))-------------------------在自旋等待过程中,如果有更高优先级进程抢占或者被调度器要求调度出去,那应该放弃自旋等待,退出 MCS 链表,跳转到 unqueue 标签处处理 MCS 链表删除节点的情况。
144  			goto unqueue;
145  
146  		cpu_relax();
147  	}
148  	return true;
149  
150  unqueue:
151  	/*
152  	 * Step - A  -- stabilize @prev
153  	 *
154  	 * Undo our @prev->next assignment; this will make @prev's
155  	 * unlock()/unqueue() wait for a next pointer since @lock points to us
156  	 * (or later).
157  	 */
158  
159  	for (;;) {
160  		if (prev->next == node &&
161  		    cmpxchg(&prev->next, node, NULL) == node)---------------如果 prev->next 等于 node,就把 NULL 赋值给 prev->next(Undo our @prev->next assignment)返回 node
162  			break;
163  
164  		/*
165  		 * We can only fail the cmpxchg() racing against an unlock(),
166  		 * in which case we should observe @node->locked becomming
167  		 * true.
168  		 */
169  		if (smp_load_acquire(&node->locked))
170  			return true;
171  
172  		cpu_relax();
173  
174  		/*
175  		 * Or we race against a concurrent unqueue()'s step-B, in which
176  		 * case its step-C will write us a new @node->prev pointer.
177  		 */
178  		prev = READ_ONCE(node->prev);
179  	}
180  
181  	/*
182  	 * Step - B -- stabilize @next
183  	 *
184  	 * Similar to unlock(), wait for @node->next or move @lock from @node
185  	 * back to @prev.
186  	 */
187  
188  	next = osq_wait_next(lock, node, prev);
189  	if (!next)
190  		return false;
191  
192  	/*
193  	 * Step - C -- unlink
194  	 *
195  	 * @prev is stable because its still waiting for a new @prev->next
196  	 * pointer, @next is stable because our @node->next pointer is NULL and
197  	 * it will wait in Step-A.
198  	 */
199  
200  	WRITE_ONCE(next->prev, prev);
201  	WRITE_ONCE(prev->next, next);
202  
203  	return false;
204  }
41  static inline struct optimistic_spin_node *
42  osq_wait_next(struct optimistic_spin_queue *lock,
43  	      struct optimistic_spin_node *node,
44  	      struct optimistic_spin_node *prev)
45  {
46  	struct optimistic_spin_node *next = NULL;
47  	int curr = encode_cpu(smp_processor_id());
48  	int old;
49  
50  	/*
51  	 * If there is a prev node in queue, then the 'old' value will be
52  	 * the prev node's CPU #, else it's set to OSQ_UNLOCKED_VAL since if
53  	 * we're currently last in queue, then the queue will then become empty.
54  	 */
55  	old = prev ? prev->cpu : OSQ_UNLOCKED_VAL;--------------如果 prev 节点存在, old 值为前 node cpu, 如果没有 prev,则设置 old为0,表示我们当前是最后一个在队列,然后会变空队列
56  
57  	for (;;) {
58  		if (atomic_read(&lock->tail) == curr &&
59  		    atomic_cmpxchg_acquire(&lock->tail, curr, old) == curr) {-----------tail等于当前 cpu,说明是队列最后一个,把 old 值 OSQ_UNLOCKED_VAL 设置给 tail. 返回 next = NULL.
60  			/*
61  			 * We were the last queued, we moved @lock back. @prev
62  			 * will now observe @lock and will complete its
63  			 * unlock()/unqueue().
64  			 */
65  			break;
66  		}
67  
68  		/*
69  		 * We must xchg() the @node->next value, because if we were to
70  		 * leave it in, a concurrent unlock()/unqueue() from
71  		 * @node->next might complete Step-A and think its @prev is
72  		 * still valid.
73  		 *
74  		 * If the concurrent unlock()/unqueue() wins the race, we'll
75  		 * wait for either @lock to point to us, through its Step-B, or
76  		 * wait for a new @node->next from its Step-C.
77  		 */
78  		if (node->next) {-----------------------如果不是队列最后一个 node, 使用 xchg 设置 node->next =NULL,跳出
79  			next = xchg(&node->next, NULL);
80  			if (next)
81  				break;
82  		}
83  
84  		cpu_relax();
85  	}
86  
87  	return next;
88  }

osq加锁有几种情况:

  • 无人持有锁,那是最理想的状态,直接返回;
  • 有人持有锁,将当前的Node加入到 osq 队列中,在没有高优先级任务抢占时,自旋等待前驱节点释放锁;
  • 自旋等待过程中,如果遇到高优先级任务抢占,那么需要做的事情就是将之前加入到 osq 队列中的当前节点,从 osq 队列中移除,移除的过程又分为三个步骤,分别是处理 prev 前驱节点的 next 指针指向、当前节点 Node 的 next 指针指向、以及将 prev 节点与 next 后继节点连接;

加锁过程中使用了原子操作,来确保正确性;
在这里插入图片描述
解锁时也分为几种情况:

  • 无人争用该锁,那直接可以释放锁;
  • 获取当前节点指向的下一个节点,如果下一个节点不为NULL,则将下一个节点解锁;
  • 当前节点的下一个节点为NULL,则调用osq_wait_next,来等待获取下一个节点,并在获取成功后对下一个节点进行解锁;

从解锁的情况可以看出,这个过程相当于锁的传递,从上一个节点传递给下一个节点;
在这里插入图片描述
在加锁和解锁的过程中,由于可能存在操作来更改 osq 队列,因此都调用了 osq_wait_next 来获取下一个确定的节点:(理论上是短暂的操作等待)
在这里插入图片描述

unqueue:

  • step A:
    如果 prev->next 为 node,prev->next = NULL
    如果 prev->next 不为 node, spin
    如果 node->locked 为 1,获取锁
  • step B:
    如果 node 为队列最后一个,设置 tail=0
    如果 node->next 为空,spin
    如果 node->next 非空,node->next = NULL
  • step C:
    unlink, next->prev=prev; prev->next=next;

场景 step-by-step

下图是两个任务竞争出队退出的情况,第二个和第三个节点几乎同时运行到step-A,然后step-B运行微妙,第二个节点先进入spin。

  1. 第二个节点通过将null分配给step-A来断开与前一个节点的连接:prev->next=null。
  2. 第三个节点通过给step-A赋值null来断开与前一个节点的连接:prev->next=null。
  3. 第二个节点 spin 直到 node->next 不为空,因为 step-B: node->next 为空
  4. 第三个节点是step-B:node->next不为null,所以替换为null,断开与下一个节点的连接。
  5. 6 第三个节点是步骤-C:next->prev = prev; prev->next = next;
  6. 在第二个节点 spin 后,只要步骤-B: node->next 变为非空,就分配空值并断开与下一个节点的连接。
  7. 9 第二个节点是步骤-C:next->prev = prev; prev->next = next;

在这里插入图片描述
下图是两个任务竞争出队退出的情况,这是第二个节点进入的稍早一点,在第三个节点进入step-A之前已经进入step-B的情况。

  1. 第二个节点通过将null分配给step-A来断开与前一个节点的连接:prev->next=null。
  2. 第二个节点断开与下一个节点的连接,用null代替step-B:node->next=null。
  3. 第三个节点 spin 直到 prev->next 不为空,因为 step-A: prev->next 为空。
  4. 5 第二个节点是step-C:将前一个节点和下一个节点相互连接。
  5. 在第三个节点 spin 后,只要 step-A: prev->next 变为非空值,就分配空值并断开与下一个节点的连接。
  6. 第三个节点断开与下一个节点的连接,用null代替step-B:node->next。
  7. 9 第三个节点是步骤-C:将前一个节点和下一个节点相互连接。

在这里插入图片描述
下图是midpath同时收到多个互斥请求时调用osq_lock()函数获取OSQ(MCS)锁的过程。

红色locked=1 将OSQ 节点中的第二个节点标记为locked=1,当调用osq_unlock() 时,第二个节点可以脱离osq_lock 自旋。

a 时刻,cpu-2 获取 osq lock
b 时刻,cpu-1 排队要获取 osq lock,进入 osq spin
c 时刻,cpu-0 排队要获取 osq lock,进入 osq spin
d 时刻,cpu-3 排队要获取 osq lock,进入 osq spin
e 时刻,cpu-2 释放锁,cpu-1获取锁,此时队列第一个节点 cpu-0 locked=1, 且 cpu-0 还在 spin
f 时刻,cpu-1释放锁,cpu-0获取锁,此时队列第一个节点 cpu-3 locked=1, 且 cpu3 还在 spin
g 时刻,cpu-0释放锁,cpu-3获取锁,此时队列只有 cpu-3,tail = 4
h 时刻,cpu-3释放锁,tail = 0

tail 始终记录 真实 cpu + 1 值

在这里插入图片描述

参考:
http://jake.dothome.co.kr/mutex/
https://www.cnblogs.com/LoyenWang/p/12826811.html

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的mosquitto客户端C代码示例: ```c #include <stdio.h> #include <mos以下是一个简单的quitto.h> 以下是一个mos简单的void on_connect(struct mosquitto *mosquitto客户端C代码示例: ```q, void *c #includeuserdata, intmos <stdio result).h> #include { quitto客户端C代码示例: ```c < if(resultmosquitto#include <.hstdio.h == 0> void on_connect(struct mosquitto) { printf("Connected *mosq> #include <, void * to broker successfully.\n"); userdata mosquitto.h } else> void on, int_connect { printf result) { (struct mosquitto *mos(" if(resultqConnection failed, void *userdata, int ==: %s 0) { result) { \n", mosquit printf("to_strerrorConnected to if(result broker successfully == 0.\n"); (result)); } } void } else) { printf("Connected on_message(struct { mosquitto printf("Connection failed to broker successfully.\n"); *mosq: %s } else\n, void * { printf", mosquitto_strerroruserdata, const("Connection failed(result)); struct mosquit: %s } } voidto_message *message) { printf(" on_message(struct mosquitReceived message: %sto\n *", (mosq, voidchar *)message *userdata->payload); , const} int main() { struct mosquitto *mosq = NULL; int rc; mosquitto_lib_init(); mosq = mosquitto_new(NULL, true, NULL); if(mosq == NULL) { printf("Failed to create mosquitto instance.\\n", mosn"); quitto_strerror(result)); return 1 } } ; void on_message(struct mosquitto *mos } mosq, voidquitto_connect_callback_set *userdata, const struct mosquitto(m_message *message) { struct mosquitto_message *messageosq, printf("Received) { printf(" message: %s\n", (char *) on_connect); Received message mosquitmessage->payload); } intto: main() { % s\n struct mosquitto *mos_message_callback_set(mos", (q = NULL; int rc; charq, on *)message-> mosquitto_lib_init(); payload_message); ); } int main mosq = rc mosquit = mosquitto_new(NULL, true() { to, struct mos NULL_connect(mosq); ifquitto *mosq, " = NULL(mosqlocalhost", ==; int rc; 1883, NULL)60); { printf("Failed mosquit toto create if mos_lib(rc !=quitto MOS instance.\_init(); n"); Q mos return 1_ERR_SUCCESS; q =) } mosquit mosquitto { to_connect_callback_new(NULL printf_set(mosq,, true(" on_connect); Unable mos, NULLquit to); to connect_message_callback_set to(mos q broker:, on_message); if %s rc = mosquitto_connect(mos\n",q, "localhost",(mos mosquitq == 1883to_strerror NULL, 60); (rc)); ) if(rc != MOSQ_ERR_SUCCESS { return) { printf(" 1Unable printf to connect to; (" broker: %Failed } s\n", to create mosquitto mosquit mosquit_strerror(rc)); return 1; } toto mosquit instanceto_subscribe(mos_subscribe.\q, NULL, "topic/test(m",n 0); mosquitto_loop_start"); (mososqq ); getchar(); return mosquit, NULL 1to_loop_stop(m, "; osq, true ); topic } mosquit/test", mosto_disconnect(mosq); 0 mosquitto_destroy(mquit); toosq); mos mosquit_connect_callbackquitto_lib_cleanup(); return_set (mosto_loop0; } ```q,_start(m onos_connectq); ); mos getchar(); quitto _message_callback mos_setquit(mtoos_loop_stopq, on(m_messageosq); 这个代码示例演示了如何使用mosquitto库创建一个简单的MQTT客户端。它连接到本地的MQTT, true rc); = mos quit mosto_connectquit(mtoos_disconnectq(mos,q "); localhost ", mos188quit3to_destroy, 代理,订阅了名为"topic/test"的主题,并在接收到消息时打印出来。你可以根据自己的需求修改代码。(m60os); q); if (rc != mos MOSquittoQ_lib_ERR_SUCCESS) { _cleanup(); return printf ("0; } Unable to``` 该代码使用了mosquitto库来创建一个mosquitto客户端实例,并连接到本地的MQTT代 connect to理。然后,它订阅了名为"topic/test"的主题,并在接收到消息时打印出来。你可以根据需要修改代码的主题和代理地址。 broker: %s\n", mosquitto_strerror(rc)); return 1; } mosquitto_subscribe(mosq, NULL, "topic/test", 0); mosquitto_loop_start(mosq); getchar(); mosquitto_loop_stop(mosq, true); mosquitto_disconnect(mosq); mosquitto_destroy(mosq); mosquitto_lib_cleanup(); return 0; } ``` 这个代码示例演示了如何使用mosquitto库创建一个简单的MQTT客户端。它连接到本地的MQTT代理,订阅了名为"topic/test"的主题,并在接收到消息时打印出来。你可以根据自己的需求修改代码来实现更复杂的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值