测试pthread_rwlock_rdlock_2-1.c用例失败。log如下:
[83#yuchen@ubuntu ltp]# gcc 2-1.c -o 21 -pthread [84#yuchen@ubuntu ltp]# ./21 main: has priority: 3 main: attempt read lock main: acquired read lock main: create wr_thread, with priority: 2 wr_thread: attempt write lock main: create rd_thread, with priority: 1 rd_thread: attempt read lock rd_thread: acquired read lock rd_thread: unlock read lock Test FAILED: rd_thread did not block on read lock, when a reader owns the lock, and a higher priority writer is waiting for the lock [85#yuchen@ubuntu ltp]# |
这个测试用例的流程:
Steps:
* We have three threads, main(also a reader), writer, reader
*
* 1. Main thread set its shcedule policy as "SCHED_FIFO", with highest priority
* the three: sched_get_priority_min()+2.
* 2. Main thread read lock 'rwlock'
* 3. Create a writer thread, with schedule policy as "SCHED_FIFO", and priority
* using sched_get_priority_min()+1.
* 4. The thread write lock 'rwlock', should block.
* 5. Main thread create a reader thread, with schedule policy as "SCHED_FIFO", and
* priority sched_get_priority_min()
* 6. Reader thread read lock 'rwlock', should block, since there is a higher priority
* writer blocked on 'rwlock'
* 7. Main thread release the 'rwlock', the writer should get the lock first
*/
用例失败的原因:
rwlock是读写锁,读者是可以递归调用的,因为多个读者不存在竞争问题,如果写者先拿到了锁,则后面来的读者必须阻塞。读和写之间必然会造成竞争,但是读和读之间没有竞争。读写锁的实现比较复杂,基于futex实现(用户态和内核态混合同步机制)。在glibc库有很多分支判断,如果对futex机制不了解的话,是比较难理解的。针对这个用例,简单说下流程:
默认情况下,如果没有写者持有锁,并且锁的属性设置了读优先(默认情况下rwlock就是读优先的),那么读者可以重复占用锁(读和读没有竞争)。代码见nptl/pthread_rwlock_rdlock.c,注意140行。那么在该测试用例中有2个读者,读者1先拿到了锁,读者2在去拿锁,是不会被阻塞的,因为写者并没有持有锁。持有锁的是一个读者(main主程序)。
但是测试用例期望,在有高优先级写者竞争锁的时候,读者要阻塞。从c库代码上看,要想让读者阻塞,必须设置rwlock写者优先属性,也就是让pthread_rwlock_rdlock.c的140行的条件判断为假。从而让代码走到slowfast,在pthread_rwlock_rdlock_slow函数中会挂起当前线程,等待其他任务unlock时才有可能被唤醒。
119 __pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
120 {
121 int result = 0;
122 bool wake = false;
123 int futex_shared =
124 rwlock->__data.__shared == LLL_PRIVATE ? FUTEX_PRIVATE : FUTEX_SHARED;
125
126 LIBC_PROBE (rdlock_entry, 1, rwlock);
127
128 if (ELIDE_LOCK (rwlock->__data.__rwelision,
129 rwlock->__data.__lock == 0
130 && rwlock->__data.__writer == 0
131 && rwlock->__data.__nr_readers == 0))
132 return 0;
133
134 /* Make sure we are alone. */
135 lll_lock (rwlock->__data.__lock, rwlock->__data.__shared);
136
137 /* Get the rwlock if there is no writer... */
138 if (rwlock->__data.__writer == 0
139 /* ...and if either no writer is waiting or we prefer readers. */
140 && (!rwlock->__data.__nr_writers_queued
141 || PTHREAD_RWLOCK_PREFER_READER_P (rwlock)))
142 {
143 /* Increment the reader counter. Avoid overflow. */
144 if (__glibc_unlikely (++rwlock->__data.__nr_readers == 0))
145 {
146 /* Overflow on number of readers. */
147 --rwlock->__data.__nr_readers;
148 result = EAGAIN;
149 }
150 else
151 {
152 LIBC_PROBE (rdlock_acquire_read, 1, rwlock);
153 /* If we are the first reader, and there are blocked readers and
154 writers (which we don't prefer, see above), then it can be the
155 case that we stole the lock from a writer that was already woken
156 to acquire it. That means that we need to take over the writer's
157 responsibility to wake all readers (see pthread_rwlock_unlock).
158 Thus, wake all readers in this case. */
159 if (rwlock->__data.__nr_readers == 1
160 && rwlock->__data.__nr_readers_queued > 0
161 && rwlock->__data.__nr_writers_queued > 0)
162 {
163 ++rwlock->__data.__readers_wakeup;
164 wake = true;
165 }
166 }
167
168 /* We are done, free the lock. */
169 lll_unlock (rwlock->__data.__lock, rwlock->__data.__shared);
170
171 if (wake)
172 futex_wake (&rwlock->__data.__readers_wakeup, INT_MAX, futex_shared);
173
174 return result;
175 }
176
177 return __pthread_rwlock_rdlock_slow (rwlock);
178 }
解决方法:
在初始化rwlock的时候,要设置一下写者优先属性。代码如下:
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
//初始化rwlock时,设置写者优先属性。
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, &attr);
测试用例代码:
/*
* Copyright (c) 2002, Intel Corporation. All rights reserved.
* This file is licensed under the GPL license. For the full content
* of this license, see the COPYING file at the top level of this
* source tree.
* Test that pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
*
* If the Thread Execution Scheduling option is supported,
* and the threads involved in the lock are executing with the
* scheduling policies SCHED_FIFO or SCHED_RR, the calling thread shall not
* acquire the lock if a writer holds the lock or if writers of
* higher or equal priority are blocked on the lock;
* otherwise, the calling thread shall acquire the lock.
*
* In this case, we test "higher priority writer block"
*
Steps:
* We have three threads, main(also a reader), writer, reader
*
* 1. Main thread set its shcedule policy as "SCHED_FIFO", with highest priority
* the three: sched_get_priority_min()+2.
* 2. Main thread read lock 'rwlock'
* 3. Create a writer thread, with schedule policy as "SCHED_FIFO", and priority
* using sched_get_priority_min()+1.
* 4. The thread write lock 'rwlock', should block.
* 5. Main thread create a reader thread, with schedule policy as "SCHED_FIFO", and
* priority sched_get_priority_min()
* 6. Reader thread read lock 'rwlock', should block, since there is a higher priority
* writer blocked on 'rwlock'
* 7. Main thread release the 'rwlock', the writer should get the lock first
*/
/* NOTE: The test result is UNSUPPORTED if Thread Execution Scheduling option
* is not supported.
*/
#define _XOPEN_SOURCE 600
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sched.h>
#include "posixtest.h"
#define TRD_POLICY SCHED_FIFO
static pthread_rwlock_t rwlock;
static int rd_thread_state;
static int wr_thread_state;
/* thread states:
1: not in child thread yet;
2: just enter child thread ;
3: just before child thread exit;
*/
#define NOT_CREATED_THREAD 1
#define ENTERED_THREAD 2
#define EXITING_THREAD 3
static int set_priority(pthread_t pid, unsigned policy, unsigned prio)
{
struct sched_param sched_param;
memset(&sched_param, 0, sizeof(sched_param));
sched_param.sched_priority = prio;
if (pthread_setschedparam(pid, policy, &sched_param) == -1) {
printf("Can't set policy to %d and prio to %d\n", policy, prio);
exit(PTS_UNRESOLVED);
}
return 0;
}
static void *fn_rd(void *arg)
{
int rc = 0;
int priority;
rd_thread_state = ENTERED_THREAD;
priority = (long)arg;
set_priority(pthread_self(), TRD_POLICY, priority);
printf("rd_thread: attempt read lock\n");
rc = pthread_rwlock_rdlock(&rwlock);
if (rc != 0) {
printf
("Test FAILED: rd_thread failed to get read lock, Error code:%d\n",
rc);
exit(PTS_FAIL);
} else
printf("rd_thread: acquired read lock\n");
sleep(1);
printf("rd_thread: unlock read lock\n");
if (pthread_rwlock_unlock(&rwlock) != 0) {
printf("rd_thread: Error at pthread_rwlock_unlock()\n");
exit(PTS_UNRESOLVED);
}
rd_thread_state = EXITING_THREAD;
pthread_exit(0);
return NULL;
}
static void *fn_wr(void *arg)
{
int rc = 0;
int priority;
wr_thread_state = ENTERED_THREAD;
priority = (long)arg;
set_priority(pthread_self(), TRD_POLICY, priority);
printf("wr_thread: attempt write lock\n");
rc = pthread_rwlock_wrlock(&rwlock);
if (rc != 0) {
printf
("Error: wr_thread failed to get write lock, Error code:%d\n",
rc);
exit(PTS_UNRESOLVED);
} else
printf("wr_thread: acquired write lock\n");
sleep(1);
printf("wr_thread: unlock write lock\n");
if (pthread_rwlock_unlock(&rwlock) != 0) {
printf("wr_thread: Error at pthread_rwlock_unlock()\n");
exit(PTS_UNRESOLVED);
}
wr_thread_state = EXITING_THREAD;
pthread_exit(0);
return NULL;
}
int main(void)
{
#ifndef _POSIX_THREAD_PRIORITY_SCHEDULING
printf("Posix Thread Execution Scheduling not supported\n");
return PTS_UNSUPPORTED;
#endif
int cnt = 0;
pthread_t rd_thread, wr_thread;
int priority;
/* main thread needs to have the highest priority */
priority = sched_get_priority_min(TRD_POLICY) + 2;
set_priority(pthread_self(), TRD_POLICY, priority);
printf("main: has priority: %d\n", priority);
if (pthread_rwlock_init(&rwlock, NULL) != 0) {
printf("main: Error at pthread_rwlock_init()\n");
return PTS_UNRESOLVED;
}
printf("main: attempt read lock\n");
/* This read lock should succeed */
if (pthread_rwlock_rdlock(&rwlock) != 0) {
printf
("Test FAILED: main cannot get read lock when no one owns the lock\n");
return PTS_FAIL;
} else
printf("main: acquired read lock\n");
wr_thread_state = NOT_CREATED_THREAD;
priority = sched_get_priority_min(TRD_POLICY) + 1;
printf("main: create wr_thread, with priority: %d\n", priority);
if (pthread_create(&wr_thread, NULL, fn_wr, (void *)(long)priority) !=
0) {
printf("main: Error at 1st pthread_create()\n");
return PTS_UNRESOLVED;
}
/* If the shared data is not altered by child after 3 seconds,
we regard it as blocked */
/* We expect the wr_thread to block */
cnt = 0;
do {
sleep(1);
} while (wr_thread_state != EXITING_THREAD && cnt++ < 3);
if (wr_thread_state == EXITING_THREAD) {
printf
("wr_thread did not block on write lock, when a reader owns the lock\n");
exit(PTS_UNRESOLVED);
} else if (wr_thread_state != ENTERED_THREAD) {
printf("Unexpected wr_thread state: %d\n", wr_thread_state);
exit(PTS_UNRESOLVED);
}
rd_thread_state = 1;
priority = sched_get_priority_min(TRD_POLICY);
printf("main: create rd_thread, with priority: %d\n", priority);
if (pthread_create(&rd_thread, NULL, fn_rd, (void *)(long)priority) !=
0) {
printf("main: failed at creating rd_thread\n");
return PTS_UNRESOLVED;
}
/* We expect the rd_thread to block */
cnt = 0;
do {
sleep(1);
} while (rd_thread_state != EXITING_THREAD && cnt++ < 3);
if (rd_thread_state == EXITING_THREAD) {
printf
("Test FAILED: rd_thread did not block on read lock, when a reader owns the lock, and a higher priority writer is waiting for the lock\n");
exit(PTS_FAIL);
} else if (rd_thread_state != ENTERED_THREAD) {
printf("Unexpected rd_thread state: %d\n", rd_thread_state);
exit(PTS_UNRESOLVED);
}
printf("main: unlock read lock\n");
if (pthread_rwlock_unlock(&rwlock) != 0) {
printf("main: failed to unlock read lock\n");
exit(PTS_UNRESOLVED);
}
/* we expect the writer get the lock */
cnt = 0;
do {
sleep(1);
} while (wr_thread_state != EXITING_THREAD && cnt++ < 3);
if (wr_thread_state == ENTERED_THREAD) {
printf
("Test FAILED: higher priority wr_thread still blocked on write lock, when a reader release the lock\n");
exit(PTS_FAIL);
} else if (wr_thread_state != EXITING_THREAD) {
printf("Unexpected wr_thread state: %d\n", wr_thread_state);
exit(PTS_UNRESOLVED);
}
if (pthread_join(wr_thread, NULL) != 0) {
printf("main: Error at 1st pthread_join()\n");
exit(PTS_UNRESOLVED);
}
/* we expect the reader get the lock when writer has unlocked the lock */
cnt = 0;
do {
sleep(1);
} while (rd_thread_state != EXITING_THREAD && cnt++ < 3);
if (rd_thread_state == ENTERED_THREAD) {
printf
("Test FAILED: rd_thread still block on read lock when the lock has no owner\n");
exit(PTS_FAIL);
} else if (rd_thread_state != EXITING_THREAD) {
printf("Unexpected rd_thread state\n");
exit(PTS_UNRESOLVED);
}
if (pthread_join(rd_thread, NULL) != 0) {
printf("main: Error at 2nd pthread_join()\n");
exit(PTS_UNRESOLVED);
}
if (pthread_rwlock_destroy(&rwlock) != 0) {
printf("Error at pthread_rwlockattr_destroy()");
return PTS_UNRESOLVED;
}
printf("Test PASSED\n");
return PTS_PASS;
}