linuxカーネル信号量学習(2.6.23(i 386))

151490 ワード

一、定義:
/linux/include/asm-i386/semaphore.h
 
 44struct semaphore {
 45 atomic_t count;
 46 int sleepers;
 47 wait_queue_head_t wait;
 48};

二、作用:
Linuxの信号量は睡眠ロックです.すでに保有されている信号量を取得しようとするタスクがある場合、信号量は待機キューに押し込まれ、スリープされます.このとき、プロセッサは他のコードを実行する自由を得る.信号量を持つプロセスが信号量を解放すると、待機キュー内のタスクが起動され、この信号量が得られる
.主にlinuxカーネルでの同期と反発に使用されます.
三、フィールドの詳細:
1、atomic_t count;
typedef struct { int counter; } atomic_t;

ここでcount.counterの値が異なるこのフィールドは、異なる意味を表します.
(1)count.counterが0より大きい場合、リソースはアイドルであり、このリソースが使用できるようになりました.
(2)count.counterが0に等しい場合、信号量は忙しいが、この保護されたリソースを待つプロセスはなく、現在は保護されたリソースにアクセスしているプロセスのみである.
(3)count.counterが0未満の場合、リソースは使用できません.少なくとも1つのプロセスがリソースを待っています.
2、int sleepers;
信号量で睡眠をとるプロセスがあるかどうかを示すフラグを格納します.信号量取得操作の際、このフィールドとcountフィールドを用いて信号量の状態を判断し、異なる操作を行う.
3、wait_queue_head_t;
 
 50struct __wait_queue_head {
 51 spinlock_t lock;
 52 struct list_head task_list;
 53};
 54typedef struct __wait_queue_head wait_queue_head_t;

 
task_Listフィールドには、現在待機している信号量のすべてのプロセスのチェーンテーブルが格納されます.もしそうならcounterが0以上であれば、チェーンテーブルは空です.
四、特徴:信号量の睡眠特性は、信号量をロックが長時間保持される場合に適用する.割り込みコンテキストではスケジューリングできないため、プロセスコンテキストでのみ使用できます.また、コードが信号量を持っている場合は、スピンロックを持ってはいけません.
1つのタスクが共有リソースにアクセスするには、まず信号量を取得しなければならない.信号量を取得する操作は信号量の値を1減少させ、現在の信号量の値が負数であれば、信号量を取得できないことを示し、そのタスクは信号量の待機キューに掛けて信号量が利用可能になるのを待たなければならない.現在の信号量の値が負でない場合、信号量が得られることを示すので、その信号量によって保護された共有リソースに直ちにアクセスすることができる.タスクが信号量によって保護された共有リソースにアクセスした後、信号量を解放しなければならない.解放信号量は、信号量の値に1を加えることによって実現され、信号量の値が非正数である場合、現在の信号量を待つタスクがあることを示すため、信号量を待つすべてのタスクを呼び覚ます.
五、操作:
1、定義と初期化:
(1)
struct semapom sem;
sema_init(&sem,1);
信号量semを直接定義し、sema_を呼び出すInit()を初期化するには、次の手順に従います.
 
 64static inline void sema_init (struct semaphore *sem, int val)
 65{
 66/*
 67 * *sem = (struct semaphore)__SEMAPHORE_INITIALIZER((*sem),val);
 68 *
 69 * i'd rather use the more flexible initialization above, but sadly
 70 * GCC 2.7.2.3 emits a bogus warning. EGCS doesn't. Oh well.
 71 */
 72 atomic_set(&sem->count, val);
 73 sem->sleepers = 0;
 74 init_waitqueue_head(&sem->wait);
 75}

 
この関数はsem->count.counterはvalに初期化されます.valは任意の整数であってもよいが、通常は1、0をとる.並列sleepersは0、sem->wait.task_Listは空のチェーンテーブルです.
(2)
srtuct semaphore sem;
init_MUTEX(&sem);
信号量semを直接定義し、反発信号量に初期化する.
 
 77static inline void init_MUTEX (struct semaphore *sem)
 78{
 79 sema_init(sem, 1);
 80}

この関数は信号量のcountを直接与える.counterを1に設定すると、反発アクセスのための信号量が初期化されます.すなわち、保護されたリソースは同時に複数のプロセスによってアクセスできず、現在リソースが空いている場合、1つのプロセスがリソースにアクセスすると、すなわち信号量が得られ、プロセスが到来し、現在リソースにアクセスしているプロセスが信号量を解放していない場合、その後のプロセスはリソースにアクセスできない.信号量の待機プロセスキューに配置され、スリープ状態になります.
(3)
struct semaphore sem;
init_MUTEX_LOCKED(&sem);
信号量semを直接定義し、同期のためにリソースビジー状態に初期化します.
 
 82static inline void init_MUTEX_LOCKED (struct semaphore *sem)
 83{
 84 sema_init(sem, 0);
 85}

すなわち、現在、この信号量はロックされており、一方の実行ユニットの継続的な実行は、他方の実行ユニットの完了を待つ必要があり、実行の前後順序を保証する.例:
プロセスAプロセスB struct semaphore sem;                ...[CODE 2]... init_MUTEX_LOCKED(&sem);        up(&sem); ...[CODE 1]... down(&sem); ...[CODE 3]...
 
以上のように、プロセスAが先に実行され、down(&sem)に実行されます.の場合、信号量がリソースビジー状態で取得できないことが判明し、信号量の待ち行列に入れられる.プロセスBがCODE 2コードセグメントをup(&sem)に実行すると、信号量が解放され、プロセスAが待機信号量であることが判明し、Aが待機キューから削除され、Aが起動する.これにより,コードの実行順序がCODE 1→CODE 2→CODE 3であることが保証される.同期が実現しました.
(4)
DECLARE_MUTEX(sem);
DECLARE_MUTEX_LOCKED(sem);
 
 51#define __SEMAPHORE_INITIALIZER(name, n) /
 52{ /
 53 .count = ATOMIC_INIT(n), /
 54 .sleepers = 0, /
 55 .wait = __WAIT_QUEUE_HEAD_INITIALIZER((name).wait) /
 56}
 57
 58#define __DECLARE_SEMAPHORE_GENERIC(name,count) /
 59 struct semaphore name = __SEMAPHORE_INITIALIZER(name,count)
 60
 61#define DECLARE_MUTEX(name) __DECLARE_SEMAPHORE_GENERIC(name,1)
 62#define DECLARE_MUTEX_LOCKED(name) __DECLARE_SEMAPHORE_GENERIC(name,0)

 
この2つのマクロは、初期化信号量を定義します.DECLARE_MUTEX()は、前述の2番目のDECLARE_に等しいMUTEX_LOCKED()は、上記の3番目に等しい.
2、取得信号量:
(1)
 
 97static inline void down(struct semaphore * sem)
 98{
 99 might_sleep();
 100 __asm__ __volatile__(
 101 "# atomic down operation/n/t"
 102 LOCK_PREFIX "decl %0/n/t" /* --sem->count */
 103 "jns 2f/n"
 104 "/tlea %0,%%eax/n/t"
 105 "call __down_failed/n"
 106 "2:"
 107 :"+m" (sem->count)
 108 :
 109 :"memory","ax");
 110}
 64void __sched
 65__down_failed(struct semaphore *sem)
 66{
 67 struct task_struct *tsk = current;
 68 DECLARE_WAITQUEUE(wait, tsk);
 69
 70#ifdef CONFIG_DEBUG_SEMAPHORE
 71 printk("%s(%d): down failed(%p)/n",
 72 tsk->comm, tsk->pid, sem);
 73#endif
 74
 75 tsk->state = TASK_UNINTERRUPTIBLE;
 76 wmb();
 77 add_wait_queue_exclusive(&sem->wait, &wait);
 78
 79 /*
 80 * Try to get the semaphore. If the count is > 0, then we've
 81 * got the semaphore; we decrement count and exit the loop.
 82 * If the count is 0 or negative, we set it to -1, indicating
 83 * that we are asleep, and then sleep.
 84 */
 85 while (__sem_update_count(sem, -1) <= 0) {
 86 schedule();
 87 set_task_state(tsk, TASK_UNINTERRUPTIBLE);
 88 }
 89 remove_wait_queue(&sem->wait, &wait);
 90 tsk->state = TASK_RUNNING;
 91
 92 /*
 93 * If there are any more sleepers, wake one of them up so
 94 * that it can either get the semaphore, or set count to -1
 95 * indicating that there are still processes sleeping.
 96 */
 97 wake_up(&sem->wait);
 98
 99#ifdef CONFIG_DEBUG_SEMAPHORE
 100 printk("%s(%d): down acquired(%p)/n",
 101 tsk->comm, tsk->pid, sem);
 102#endif
 103}

コードから次のことがわかります.
もしそうならcounterが1であればcountを置く.counterは0で、直接関数から飛び出します.
もしそうならcounterは0、count.counterは-1に減算され、その後実行されます_down_failed()関数.
__down_failed()関数は、まず現在のプロセスを中断不可状態(TASK_UNINTERUPTIBLE)に設定します.
)を待機プロセスキューに追加し、whlieサイクルで信号量を取得しようとします.もしそうならcounterが0より大きいと信号量が得られ、ループに入らず現在のプロセスを待ち行列から削除し、その状態を実行可能状態(TASK_RUNNING)に設定し、最後にその信号量を待つプロセスを起動する(ここでは初期count.conterは0であり、待ち行列がないことを示すので、この文はないに相当する).そうでなければcounterは-1に設定され、whlieループに入り、現在のプロセスを保留し、その後回復し、countのテストを継続します.counterフィールドは、0より大きい(すなわち、信号量が得られる)まで.
もしそうならcounterが-1の場合は-2に設定され、その後実行されます_down_failed()関数.とcount.counterが0に等しいのとは異なり、信号量が得られた後、待機プロセス(count.connter=−1)があるため、終了時に待機プロセスが起動する.
信号量は睡眠を招くため、中断コンテキストでは使用できません.またdown()を使用して睡眠に入るプロセスは信号で中断されません.
(2)
 
 112/*
 113 * Interruptible try to acquire a semaphore. If we obtained
 114 * it, return zero. If we were interrupted, returns -EINTR
 115 */
 116static inline int down_interruptible(struct semaphore * sem)
 117{
 118 int result;
 119
 120 might_sleep();
 121 __asm__ __volatile__(
 122 "# atomic interruptible down operation/n/t"
 123 "xorl %0,%0/n/t"
 124 LOCK_PREFIX "decl %1/n/t" /* --sem->count */
 125 "jns 2f/n/t"
 126 "lea %1,%%eax/n/t"
 127 "call __down_failed_interruptible/n"
 128 "2:"
 129 :"=&a" (result), "+m" (sem->count)
 130 :
 131 :"memory");
 132 return result;
 133}
 105int __sched
 106__down_failed_interruptible(struct semaphore *sem)
 107{
 108 struct task_struct *tsk = current;
 109 DECLARE_WAITQUEUE(wait, tsk);
 110 long ret = 0;
 111
 112#ifdef CONFIG_DEBUG_SEMAPHORE
 113 printk("%s(%d): down failed(%p)/n",
 114 tsk->comm, tsk->pid, sem);
 115#endif
 116
 117 tsk->state = TASK_INTERRUPTIBLE;
 118 wmb();
 119 add_wait_queue_exclusive(&sem->wait, &wait);
 120
 121 while (__sem_update_count(sem, -1) <= 0) {
 122 if (signal_pending(current)) {
 123 /*
 124 * A signal is pending - give up trying.
 125 * Set sem->count to 0 if it is negative,
 126 * since we are no longer sleeping.
 127 */
 128 __sem_update_count(sem, 0);
 129 ret = -EINTR;
 130 break;
 131 }
 132 schedule();
 133 set_task_state(tsk, TASK_INTERRUPTIBLE);
 134 }
 135
 136 remove_wait_queue(&sem->wait, &wait);
 137 tsk->state = TASK_RUNNING;
 138 wake_up(&sem->wait);
 139
 140#ifdef CONFIG_DEBUG_SEMAPHORE
 141 printk("%s(%d): down %s(%p)/n",
 142 current->comm, current->pid,
 143 (ret < 0 ? "interrupted" : "acquired"), sem);
 144#endif
 145 return ret;
 146}

 
コードからdown_がわかりますinterruptible()とdown()の違いは、down_interruptible()には戻り値があり、
呼び出し_down_failed_interruptible()関数の場合、whileループでは少し違います._down_failed_interruptible()がwhileにある場合、TIF_を受け取るSIGPENDING信号の場合はcount.counterは0で、ループを飛び出して、down_が表示されます.interruptible()は、信号によって遮断され、非ゼロ(EINIR)を返すことができる.
 
(3)
 
 139static inline int down_trylock(struct semaphore * sem)
 140{
 141 int result;
 142
 143 __asm__ __volatile__(
 144 "# atomic interruptible down operation/n/t"
 145 "xorl %0,%0/n/t"
 146 LOCK_PREFIX "decl %1/n/t" /* --sem->count */
 147 "jns 2f/n/t"
 148 "lea %1,%%eax/n/t"
 149 "call __down_failed_trylock/n/t"
 150 "2:/n"
 151 :"=&a" (result), "+m" (sem->count)
 152 :
 153 :"memory");
 154 return result;
 155}

 
この関数は、信号量semを取得しようと試み、すぐに取得できれば、信号量semを取得し、0を返し、そうでなければ0を返さない.呼び出し元がスリープすることはなく、コンテキストを中断して使用できます.
3、解放信号量:
 
 168static inline void up(struct semaphore * sem)
 169{
 170 __asm__ __volatile__(
 171 "# atomic up operation/n/t"
 172 LOCK_PREFIX "incl %0/n/t" /* ++sem->count */
 173 "jg 1f/n/t"
 174 "call __up_wakeup/n"
 175 "1:"
 176 :"=m" (sem->count)
 177 :"D" (sem)
 178 :"memory");
 179}
 148void
 149__up_wakeup(struct semaphore *sem)
 150{
 151 /*
 152 * Note that we incremented count in up() before we came here,
 153 * but that was ineffective since the result was <= 0, and
 154 * any negative value of count is equivalent to 0.
 155 * This ends up setting count to 1, unless count is now > 0
 156 * (i.e. because some other cpu has called up() in the meantime),
 157 * in which case we just increment count.
 158 */
 159 __sem_update_count(sem, 1);
 160 wake_up(&sem->wait);
 161}

 
up()関数はまずcount.counterは自己増加し、0より大きい場合は、初期値が0であり、プロセスを待たないため、直接飛び出します.そうでなければ呼び出し_up_wake_up()関数はcount.counterを1に設定し、待機プロセスを起動します.
六、使用例:
信号量の一般的な使用形態は、
DECLARE_MUTEX(sem);
down(&sem);//取得信号量
...[CODE]...//りんかいいき
up(&sem);//かいほうしんごうりょう
七、信号量とスピンロックの比較:
信号量は、複数のプロセス間のリソースに対する反発に使用されるプロセスレベルであり、カーネルでもあるが、カーネル実行パスはプロセスとしてプロセスを代表してリソースを競合する.競合に失敗すると、プロセスコンテキストの切り替えが発生し、現在のプロセスがスリープ状態になり、CPUは他のプロセスを実行します.プロセスコンテキスト切替のオーバーヘッドも大きいため、プロセスがリソースを占有する時間が長い場合にのみ、信号量を用いることが好ましい.保護する臨界領域へのアクセス時間が比較的短い場合、コンテキスト切替の時間を節約するため、スピンロックを使用することは非常に便利である.しかし、CPUはスピンロックがそこで空回りして他の実行ユニットのロック解除を知るまで得られない.したがって,ロックは臨界領域に常時滞留できないことが要求され,そうしないとシステムの効率が低下する.
スピンロック対信号量-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------優先使用スピンロック長期ロック優先使用信号量割り込みコンテキストにおけるロック使用スピンロック保持ロックはスリープ、スケジューリング使用信号量を必要とする