實作 spinlock on raspberry pi 2
作者:descent 发布于:2015-11-5 9:33
process 同步機制有 spinlock, mutex, semaphore, 我的學習方式是簡化再簡化, 然後用程式碼實作他們, 否則我只會有「名詞」上的理解, 而不會真的理解。有了 spinlock 就有了基本的 process 同步機制。
以下的參考資料幫助我完成這個實作 (感謝蜗窝科技):
- Linux内核同步机制之(一):原子操作
- Linux内核同步机制之(四):spin lock
- Linux设备驱动开发详解:基于最新的Linux4.0内核 p164 代碼清單 7.1 , 以及第七章, 這部份把容易搞混的 memory barrier, compiler/cpu 的亂序執行做了一個很好的說明。
由於要分解這些功能並簡化他們, 通常我會設計簡單的測試方式並輔以實作的小程式來理解他們, spinlock 的測試讓我大傷腦筋, 之前的想法是要先實作 process switch, 才能測試 spinlock, 我也的確做了某些成果, 不過在我愈來愈會設計這些小實驗後, 已經變得很擅長這件事情。我想到一個更好的方法, 不用先實作 process switch, 我在 rpi2 寫個 pthread 程式, 然後呼叫我自己寫的 my_spin_lock, my_spin_unlock 取代 pthread_spin_lock, pthread_spin_unlock, 若行為一樣, 就代表我成功了。
spinlock.c 1 #include "spinlock.h" 2 3 #include <stdio.h> 4 37 38 void spinlock_init(Spinlock *spinlock) 39 { 40 spinlock->val_ = 0; 41 } 42 43 int spin_lock(Spinlock *spinlock) 44 { 77 #if 0 78 lock_mutex: 79 if(spinlock->val_ == 1) 80 goto lock_mutex; 81 else 82 spinlock->val_ = 1; 83 84 #endif 103 unsigned long tmp; 104 int result; 105 106 __asm__ __volatile__("@ atomic_add\n" 107 "1: ldrex %0, [%3]\n" 108 " cmp %0, #1\n" 109 "beq 1b\n" 110 "mov %0, #1\n" 111 " strex %1, %0, [%3]\n" 112 " teq %1, #0\n" 113 " bne 1b" 114 : "=&r" (result), "=&r" (tmp), "+Qo"(spinlock->val_) 115 : "r" (&spinlock->val_) 116 : "cc"); 118 } 119 120 int spin_unlock(Spinlock *spinlock) 121 { 122 spinlock->val_ = 0; 123 } 124 125 #ifdef TEST 126 #include <pthread.h> 127 #include <signal.h> 128 129 FILE *fs; 130 Spinlock sp; 131 pthread_spinlock_t spinlock; 132 133 int run=1; 134 135 void* write_file_1(void *p) 136 { 137 int time=0; 138 pthread_t tid = pthread_self(); 139 140 while(run) 141 { 142 #ifdef PTHREAD_FUNC 143 pthread_spin_lock(&spinlock); 144 #else 145 spin_lock(&sp); 146 #endif 147 //printf("111\n"); 148 fprintf(fs, "%d ## thread 1 ## %d\n", time, tid); 149 fprintf(fs, "%d ## thread 11\n", time); 150 fprintf(fs, "%d ## thread 111\n", time); 151 #if 1 152 #ifdef PTHREAD_FUNC 153 pthread_spin_unlock(&spinlock); 154 #else 155 spin_unlock(&sp); 156 #endif 157 #endif 158 ++time; 159 } 160 // printf("thread 1 exit\n"); 161 } 162 163 void* write_file_2(void *p) 164 { 165 int time = 0; 166 pthread_t tid = pthread_self(); 167 168 while(run) 169 { 170 #ifdef PTHREAD_FUNC 171 pthread_spin_lock(&spinlock); 172 #else 173 spin_lock(&sp); 174 #endif 175 //printf("222\n"); 176 fprintf(fs, "%d ## thread 2 long string 0123456789 ## %d\n", time, tid); 177 fprintf(fs, "%d ## thread 22 long string 0123456789\n", time); 178 fprintf(fs, "%d ## thread 222 long string 0123456789\n", time); 179 #ifdef PTHREAD_FUNC 180 pthread_spin_unlock(&spinlock); 181 #else 182 spin_unlock(&sp); 183 #endif 184 ++time; 185 } 186 // printf("thread 2 exit\n"); 187 } 188 189 void* write_file_3(void *p) 190 { 191 int time = 0; 192 pthread_t tid = pthread_self(); 193 int i; 194 195 while(run) 196 { 197 #ifdef PTHREAD_FUNC 198 pthread_spin_lock(&spinlock); 199 #else 200 spin_lock(&sp); 201 #endif 202 fprintf(fs, "%d ## thread 3 long string ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()~ ## %d\n", time, tid); 203 fprintf(fs, "%d ## thread 33 long string ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()~\n", time); 204 fprintf(fs, "%d ## thread 333 long string ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()~\n", time); 205 #ifdef PTHREAD_FUNC 206 pthread_spin_unlock(&spinlock); 207 #else 208 spin_unlock(&sp); 209 #endif 210 ++time; 211 } 212 // printf("thread 2 exit\n"); 213 } 214 215 static void* sig_thread (void *arg) 216 { 217 sigset_t *set = arg; 218 int s, sig; 219 220 printf("sig thread pid: %d\n", getpid()); 221 222 for (;;) 223 { 224 s = sigwait (set, &sig); 225 if (s == 0) 226 { 227 printf ("Signal handling thread got signal %d\n", sig); 228 if (sig == SIGINT) 229 { 230 run = 0; 231 //break; 232 } 233 } 234 } 235 } 236 237 int ret1, ret2, ret3; 238 239 int main(int argc, char *argv[]) 240 { 241 pthread_t thread0, thread1, thread2, thread; 242 sigset_t set; 243 244 #ifdef PTHREAD_FUNC 245 pthread_spin_init(&spinlock, 0); 246 printf("init pthread spinlock\n"); 247 #else 248 spinlock_init(&sp); 249 printf("init my spinlock\n"); 250 #endif 251 252 sigemptyset (&set); 253 sigaddset (&set, SIGQUIT); 254 sigaddset (&set, SIGINT); 255 int s = pthread_sigmask (SIG_BLOCK, &set, NULL); 256 if (s != 0) 257 { 258 perror("pthread_sigmask"); 259 return -1; 260 } 261 262 263 printf("open %s to write\n", FN); 264 fs = fopen(FN, "w"); 265 if (fs == NULL) 266 { 267 perror("open fail"); 268 return -1; 269 } 270 271 pthread_create (&thread, NULL, &sig_thread, (void *) &set); 272 pthread_create(&thread0, NULL, write_file_1, NULL); 273 pthread_create(&thread1, NULL, write_file_2, NULL); 274 pthread_create(&thread2, NULL, write_file_3, NULL); 275 276 pthread_join(thread0, (void **)&ret1); 277 pthread_join(thread1, (void **)&ret2); 278 pthread_join(thread2, (void **)&ret3); 279 280 fclose(fs); 281 printf("test end\n"); 282 283 return 0; 284 } 285 #endif
spinlock.c L77 ~ 84 是 c 版本的演算法, 為什麼不能用這個版本, 因為需要 atomic 操作, 這大家都知道, 不多說了; 困難的是怎麼實作 atomic, spinlock.c L103 ~ 116 是我參考 linux 3.10.37 arch/arm/include/asm/atomic.h atomic_add 改出來的 (因為我自己寫的都有問題 ... 冏), 這 inline assembly 實在太難, 我沒能完全看懂, ldrex/strex 可以在 armv6, cortex armv7-A 上使用, 我在 rpi2 執行這個測試, 這個版本應該也可以在 cortex m3 上執行, 這才是我真正的目的。
程式有 3 個 thread 在寫同一個檔案 /tmp/xyz1, 按下 ctrl-c 會收到 SIGINT, 程式會正確的處理這個 SIGINT 然後結束整個程式 (這並不容易, 請參考《thread 和 signal》)。 spinlock test result 為失敗與成功的內容。一開始的實作並不正確, 所以我特別用了 pthread_spin_lock, pthread_spin_unlock 來測試, 發現使用 pthread_spin_lock/pthread_spin_unlock 就會得到正確的結果。
spinlock test result:
失敗: 237 ## thread 1 ## 1985418336 237 ## thread 3 long string ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()~ ## 1968641120 206 ## thread 2 long string 0123456789 ## 1977029728 成功: 6 ## thread 1 ## 1985877088 6 ## thread 11 6 ## thread 111 0 ## thread 2 long string 0123456789 ## 1977488480 0 ## thread 22 long string 0123456789 0 ## thread 222 long string 0123456789 7 ## thread 1 ## 1985877088 7 ## thread 11 7 ## thread 111 1 ## thread 2 long string 0123456789 ## 1977488480 1 ## thread 22 long string 0123456789 1 ## thread 222 long string 0123456789
每個 thread 應該以 3 行為輸出單位, 若是被混在一起, 表示雖然有某個 thread 取得了 spinlock, 但其他的
thread 並沒有 busy loop, 而是也取得了同樣的 spinlock 並進入了 critical section,
造成了失敗的結果。
check_result.c 則是用來檢查 /tmp/xyz1 是否是正確。整個測試在 rpi2 上完成。
不知道是不是還需要 memory barrier, dsb, isb, dmb 這些指令。驗證 spinlock function
是否正確非常困難, 我也不確定這個版本一定是對的, 因為只要有一個失敗案例, 這個 spinlock 實作就不正確了, 若用上了有 bug 的
spinlock function, 那程式會很難除錯, 若核電廠有這樣的程式碼應該會嚇死不少程式人。
spinlock 做出來了, 那 mutex 呢? 把 spinlock.c L109 改成讓出 cpu 的程式碼就可以了, 這就是俗稱的去「睡覺」。
實作完 spinlock 後還有使用 spinlock 的議題, 非常的複雜, 感覺起來 mutex 是比較好的, 那為什麼還要設計 spinlock, 又中斷部份的程式碼為什麼要 spinlock 而不用 mutex 呢?
source code:
https://github.com/descent/progs/tree/master/spinlock
ref:
- 理解 Memory barrier (内存屏障)
- Memory Barrier in Lock API
- Linux x86 spinlock 實現之分析
- 分析Linux中Spinlock在ARM及X86平台上的实现
标签: spinlock

功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
发表评论: