]> git.saurik.com Git - apple/xnu.git/blobdiff - tests/turnstiles_test.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / tests / turnstiles_test.c
index 34b9667f31c416d0517abc15b5a470ffa7d10532..64636f539a8b7c67f7253a9111a113a39e60d9ff 100644 (file)
 #include <sys/sysctl.h>
 #include <sys/types.h>
 
-#define SYSCTL_TURNSTILE_TEST_DEFAULT                   1
-#define SYSCTL_TURNSTILE_TEST_GLOBAL_HASHTABLE          2
-
+#define SYSCTL_TURNSTILE_TEST_USER_DEFAULT            1
+#define SYSCTL_TURNSTILE_TEST_USER_HASHTABLE          2
+#define SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT          3
+#define SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE        4
 
 T_GLOBAL_META(T_META_NAMESPACE("xnu.turnstiles_test"));
 
@@ -48,7 +49,7 @@ thread_create_at_qos(qos_class_t qos, void * (*function)(void *), int type)
 }
 
 static int
-get_pri(thread_t thread_port)
+get_sched_pri(thread_t thread_port)
 {
        kern_return_t kr;
 
@@ -61,6 +62,20 @@ get_pri(thread_t thread_port)
        return extended_info.pth_curpri;
 }
 
+static int
+get_base_pri(thread_t thread_port)
+{
+       kern_return_t kr;
+
+       thread_extended_info_data_t extended_info;
+       mach_msg_type_number_t count = THREAD_EXTENDED_INFO_COUNT;
+       kr = thread_info(thread_port, THREAD_EXTENDED_INFO,
+           (thread_info_t)&extended_info, &count);
+
+       T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_info");
+       return extended_info.pth_priority;
+}
+
 static void
 turnstile_prim_lock(int type)
 {
@@ -68,7 +83,7 @@ turnstile_prim_lock(int type)
        uint64_t tid;
        int in_val = type;
        pthread_threadid_np(NULL, &tid);
-       T_LOG("sysctlbyname lock called from thread %llu \n", tid);
+       T_LOG("sysctlbyname lock type %d called from thread %llu \n", type, tid);
        ret = sysctlbyname("kern.turnstiles_test_lock", NULL, 0, &in_val, sizeof(in_val));
        T_LOG("sysctlbyname lock returned from thread %llu with value %d \n", tid, ret);
 }
@@ -80,15 +95,84 @@ turnstile_prim_unlock(int type)
        uint64_t tid;
        int in_val = type;
        pthread_threadid_np(NULL, &tid);
-       T_LOG("sysctlbyname unlock called from thread %llu \n", tid);
+       T_LOG("sysctlbyname unlock type %d called from thread %llu \n", type, tid);
        ret = sysctlbyname("kern.turnstiles_test_unlock", NULL, 0, &in_val, sizeof(in_val));
        T_LOG("sysctlbyname unlock returned from thread %llu with value %d \n", tid, ret);
 }
 
+struct thread_data {
+       int pri_to_set;
+       int lock1;
+       int lock2;
+       unsigned int sleep;
+       int sched_pri_to_check;
+       int base_pri_to_check;
+};
+
+static void *
+chain_locking(void* args)
+{
+       struct thread_data* data = (struct thread_data*) args;
+       int policy, pri;
+       int ret;
+       struct sched_param param;
+
+       /* Change our priority to pri_to_set */
+       ret = pthread_getschedparam(pthread_self(), &policy, &param);
+       T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "pthread_getschedparam");
+
+       param.sched_priority = data->pri_to_set;
+
+       /* this sets both sched and base pri */
+       ret = pthread_setschedparam(pthread_self(), policy, &param);
+       T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "pthread_setschedparam");
+
+       pri = get_sched_pri(mach_thread_self());
+
+       T_ASSERT_EQ(pri, data->pri_to_set, "Priority before holding locks");
+
+       /* take lock1 */
+       if (data->lock1) {
+               turnstile_prim_lock(data->lock1);
+       }
+
+       /* take lock2 */
+       if (data->lock2) {
+               turnstile_prim_lock(data->lock2);
+       }
+
+       if (data->sleep) {
+               sleep(data->sleep);
+       }
+
+       if (data->sched_pri_to_check) {
+               pri = get_sched_pri(mach_thread_self());
+               T_ASSERT_EQ(pri, data->sched_pri_to_check, "Sched priority while holding locks");
+       }
+
+       if (data->base_pri_to_check) {
+               pri = get_base_pri(mach_thread_self());
+               T_ASSERT_EQ(pri, data->base_pri_to_check, "Base priority while holding locks");
+       }
+
+       if (data->lock2) {
+               turnstile_prim_unlock(data->lock2);
+       }
+
+       if (data->lock1) {
+               turnstile_prim_unlock(data->lock1);
+       }
+
+       pri = get_sched_pri(mach_thread_self());
+       T_ASSERT_EQ(pri, data->pri_to_set, "Priority after releasing locks");
+
+       return NULL;
+}
+
 static void *
 take_lock_check_priority(void * arg)
 {
-       int old_pri = get_pri(mach_thread_self());
+       int old_pri = get_base_pri(mach_thread_self());
        int unboosted_pri;
        int boosted_pri;
        int after_unlock_pri;
@@ -102,20 +186,20 @@ take_lock_check_priority(void * arg)
        /* Take the test lock */
        turnstile_prim_lock(type);
 
-       unboosted_pri =  get_pri(mach_thread_self());
+       unboosted_pri = get_base_pri(mach_thread_self());
        T_ASSERT_EQ(unboosted_pri, 37, "thread(%llu) priority after acquiring the lock (uncontended) is %d\n", tid, unboosted_pri);
 
        sleep(8);
 
        /* Check for elevated priority */
-       boosted_pri =  get_pri(mach_thread_self());
+       boosted_pri =  get_base_pri(mach_thread_self());
        T_ASSERT_EQ(boosted_pri, 47, "thread(%llu) priority after contention by 47 thread is %d\n", tid, boosted_pri);
 
        /* Drop the lock */
        turnstile_prim_unlock(type);
 
        /* Check for regular priority */
-       after_unlock_pri =  get_pri(mach_thread_self());
+       after_unlock_pri =  get_base_pri(mach_thread_self());
        T_ASSERT_EQ(after_unlock_pri, 37, "thread(%llu) priority after dropping lock is %d\n", tid, after_unlock_pri);
 
        return NULL;
@@ -130,7 +214,7 @@ try_to_take_lock_and_unlock(void *arg)
        pthread_threadid_np(NULL, &tid);
        sleep(4);
 
-       int old_pri = get_pri(mach_thread_self());
+       int old_pri = get_base_pri(mach_thread_self());
        T_ASSERT_EQ(old_pri, 47, "thread(%llu) priority before acquiring the lock is %d\n", tid, old_pri);
 
        /* Try taking the test lock */
@@ -143,7 +227,7 @@ try_to_take_lock_and_unlock(void *arg)
 static void *
 take_lock_and_exit(void * arg)
 {
-       int old_pri = get_pri(mach_thread_self());
+       int old_pri = get_base_pri(mach_thread_self());
        int unboosted_pri;
        int boosted_pri;
        uint64_t tid;
@@ -156,13 +240,13 @@ take_lock_and_exit(void * arg)
        /* Take the test lock */
        turnstile_prim_lock(type);
 
-       unboosted_pri =  get_pri(mach_thread_self());
+       unboosted_pri =  get_base_pri(mach_thread_self());
        T_ASSERT_EQ(unboosted_pri, 37, "thread(%llu) priority after acquiring the lock (uncontended) is %d\n", tid, unboosted_pri);
 
        sleep(8);
 
        /* Check for elevated priority */
-       boosted_pri =  get_pri(mach_thread_self());
+       boosted_pri =  get_base_pri(mach_thread_self());
        T_ASSERT_EQ(boosted_pri, 47, "thread(%llu) priority after contention by 47 thread is %d\n", tid, boosted_pri);
 
        /* return without unlocking the lock */
@@ -178,7 +262,7 @@ unlock_an_owner_exited_lock(void *arg)
        pthread_threadid_np(NULL, &tid);
        sleep(12);
 
-       int old_pri = get_pri(mach_thread_self());
+       int old_pri = get_base_pri(mach_thread_self());
        T_ASSERT_EQ(old_pri, 47, "thread(%llu) priority before acquiring the lock is %d\n", tid, old_pri);
 
        /* Unlock the test lock causing the turnstile code to call thread_deallocate_safe */
@@ -246,13 +330,166 @@ test3(int type)
        return;
 }
 
-T_DECL(turnstile_test, "Turnstile test", T_META_ASROOT(YES))
+/*
+ * Test 4: test if a chain of user-space turnstile primitives followed by kernel primitives works correctly.
+ */
+static void
+test4(void)
 {
-       test1(SYSCTL_TURNSTILE_TEST_DEFAULT);
-       test2(SYSCTL_TURNSTILE_TEST_DEFAULT);
-       test3(SYSCTL_TURNSTILE_TEST_DEFAULT);
+       pthread_t threads[5] = {};
+       struct thread_data data[5] = {};
+
+       T_LOG("Test 4: test if a chain of user-space turnstile primitives followed by kernel primitives works correctly");
+
+       /*
+        * Chain: t4->ud->t3->uh->t2->kh->t1->kd->t0
+        * ud and uh (user space turnstiles) will push base pri and sched pri
+        * kd and kh (kernel space turnstiles) will push sched pri
+        * sched pri should be propagated up to the end
+        * kh is the breaking point of the chain for sched pri
+        */
+
+
+       /* Create a thread at priority 4 and take SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT lock */
+       data[0].pri_to_set = 4;
+       data[0].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be not locked */
+       data[0].lock2 = NULL;
+       data[0].sleep = 10; /* long sleep, nothing is blocking this thread */
+       data[0].sched_pri_to_check = 60;
+       data[0].base_pri_to_check = 4;
+       pthread_create(&threads[0], NULL, chain_locking, (void *)&data[0]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 31 and take SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT */
+       data[1].pri_to_set = 31;
+       data[1].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be not locked */
+       data[1].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be locked */
+       data[1].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
+       data[1].sched_pri_to_check = 60;
+       data[1].base_pri_to_check = 31;
+       pthread_create(&threads[1], NULL, chain_locking, (void *)&data[1]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 40 and take SYSCTL_TURNSTILE_TEST_USER_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE */
+       data[2].pri_to_set = 40;
+       data[2].lock1 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be not locked */
+       data[2].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be locked */
+       data[2].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
+       data[2].sched_pri_to_check = 60;
+       data[2].base_pri_to_check = 60;
+       pthread_create(&threads[2], NULL, chain_locking, (void *)&data[2]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 47 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT lock followed by SYSCTL_TURNSTILE_TEST_USER_HASHTABLE */
+       data[3].pri_to_set = 47;
+       data[3].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be not locked */
+       data[3].lock2 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be locked */
+       data[3].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
+       data[3].sched_pri_to_check = 60;
+       data[3].base_pri_to_check = 60;
+       pthread_create(&threads[3], NULL, chain_locking, (void *)&data[3]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 60 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT */
+       data[4].pri_to_set = 60;
+       data[4].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be locked */
+       data[4].lock2 = NULL;
+       data[4].sleep = 0; /* no need to sleep, nothing should be pushing by the time it acquires the lock */
+       data[4].sched_pri_to_check = 60; /* this is its own priority */
+       data[4].base_pri_to_check = 60;
+       pthread_create(&threads[4], NULL, chain_locking, (void *)&data[4]);
 
-       test1(SYSCTL_TURNSTILE_TEST_GLOBAL_HASHTABLE);
-       test2(SYSCTL_TURNSTILE_TEST_GLOBAL_HASHTABLE);
-       test3(SYSCTL_TURNSTILE_TEST_GLOBAL_HASHTABLE);
+       sleep(16);
+       return;
+}
+
+/*
+ * Test 5: test if a chain of user-space turnstile primitives interleaved by kernel primitives works correctly.
+ */
+static void
+test5(void)
+{
+       pthread_t threads[5] = {};
+       struct thread_data data[5] = {};
+
+       T_LOG("Test 5: test if a chain of user-space turnstile primitives interleaved by kernel primitives works correctly");
+
+       /*
+        * Chain: t4->ud->t3->kh->t2->uh->t1->kd->t0
+        * ud and uh (user space turnstiles) will push base pri and sched pri
+        * kd and kh (kernel space turnstiles) will push sched pri
+        * uh is the breaking point of the chain for sched pri
+        */
+
+       /* Create a thread at priority 4 and take SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT lock */
+       data[0].pri_to_set = 4;
+       data[0].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be not locked */
+       data[0].lock2 = NULL;
+       data[0].sleep = 10; /* long sleep, nothing is blocking this thread */
+       data[0].sched_pri_to_check = 41;
+       data[0].base_pri_to_check = 4;
+       pthread_create(&threads[0], NULL, chain_locking, (void *)&data[0]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 31 and take SYSCTL_TURNSTILE_TEST_USER_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT */
+       data[1].pri_to_set = 31;
+       data[1].lock1 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be not locked */
+       data[1].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_DEFAULT; /* this should be locked */
+       data[1].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
+       data[1].sched_pri_to_check = 41;
+       data[1].base_pri_to_check = 41;
+       pthread_create(&threads[1], NULL, chain_locking, (void *)&data[1]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 41 and take SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE lock followed by SYSCTL_TURNSTILE_TEST_USER_HASHTABLE */
+       data[2].pri_to_set = 41;
+       data[2].lock1 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be not locked */
+       data[2].lock2 = SYSCTL_TURNSTILE_TEST_USER_HASHTABLE; /* this should be locked */
+       data[2].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
+       data[2].sched_pri_to_check = 60;
+       data[2].base_pri_to_check = 41;
+       pthread_create(&threads[2], NULL, chain_locking, (void *)&data[2]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 47 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT lock followed by SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE */
+       data[3].pri_to_set = 47;
+       data[3].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be not locked */
+       data[3].lock2 = SYSCTL_TURNSTILE_TEST_KERNEL_HASHTABLE; /* this should be locked */
+       data[3].sleep = 0; /* no need to sleep, everything should be pushing by the time it acquires the lock */
+       data[3].sched_pri_to_check = 60;
+       data[3].base_pri_to_check = 60;
+       pthread_create(&threads[3], NULL, chain_locking, (void *)&data[3]);
+       sleep(2); /* give the thread time to acquire the lock */
+
+       /* Create a thread at priority 60 and take SYSCTL_TURNSTILE_TEST_USER_DEFAULT */
+       data[4].pri_to_set = 60;
+       data[4].lock1 = SYSCTL_TURNSTILE_TEST_USER_DEFAULT; /* this should be locked */
+       data[4].lock2 = NULL;
+       data[4].sleep = 0; /* no need to sleep, nothing should be pushing by the time it acquires the lock */
+       data[4].sched_pri_to_check = 60; /* this is its own priority */
+       data[4].base_pri_to_check = 60;
+       pthread_create(&threads[4], NULL, chain_locking, (void *)&data[4]);
+
+       sleep(16);
+       return;
+}
+
+T_DECL(turnstile_test, "Turnstile test", T_META_ASROOT(YES))
+{
+       test1(SYSCTL_TURNSTILE_TEST_USER_DEFAULT);
+       test2(SYSCTL_TURNSTILE_TEST_USER_DEFAULT);
+       test3(SYSCTL_TURNSTILE_TEST_USER_DEFAULT);
+
+       test1(SYSCTL_TURNSTILE_TEST_USER_HASHTABLE);
+       test2(SYSCTL_TURNSTILE_TEST_USER_HASHTABLE);
+       test3(SYSCTL_TURNSTILE_TEST_USER_HASHTABLE);
+
+       /*
+        * rdar://problem/46302128
+        * These tests are using a sysctl to lock a dummy kernel resource that uses turnstile.
+        * However a thread holding a kernel push from turnstile should never return in
+        * userspace, and rdar://problem/24194397 adds an assert for it.
+        */
+       //test4();
+       //test5();
 }