diff --git a/libc-test/build.rs b/libc-test/build.rs
index cdfe358488d52d872aca2ac9744f3ae65bfbfd3b..260772c89cf07ac320be27be7de6c90c4c0ef73c 100644
--- a/libc-test/build.rs
+++ b/libc-test/build.rs
@@ -96,6 +96,7 @@ fn main() {
         cfg.header("termios.h");
         cfg.header("poll.h");
         cfg.header("syslog.h");
+        cfg.header("semaphore.h");
     }
 
     if android {
@@ -285,6 +286,8 @@ fn main() {
             // uuid_t is a struct, not an integer.
             "uuid_t" if dragonfly => true,
             n if n.starts_with("pthread") => true,
+            // sem_t is a struct or pointer
+            "sem_t" if openbsd || freebsd || rumprun => true,
 
             // windows-isms
             n if n.starts_with("P") => true,
@@ -363,6 +366,10 @@ fn main() {
             // we turn into an error) so just ignore it.
             "daemon" if apple => true,
 
+            // Deprecated on OSX
+            "sem_destroy" if apple => true,
+            "sem_init" if apple => true,
+
             // These functions presumably exist on netbsd but don't look like
             // they're implemented on rumprun yet, just let them slide for now.
             // Some of them look like they have headers but then don't have
diff --git a/src/unix/bsd/apple/mod.rs b/src/unix/bsd/apple/mod.rs
index 6dceb068c42f4de184ce5b03c053d04d302d07d2..2480c82c72cdec39a62d959ab0649e76c78525de 100644
--- a/src/unix/bsd/apple/mod.rs
+++ b/src/unix/bsd/apple/mod.rs
@@ -20,6 +20,7 @@ pub type speed_t = ::c_ulong;
 pub type tcflag_t = ::c_ulong;
 pub type nl_item = ::c_int;
 pub type id_t = ::c_uint;
+pub type sem_t = ::c_int;
 
 pub enum timezone {}
 
@@ -1248,6 +1249,8 @@ pub const PRIO_DARWIN_PROCESS: ::c_int = 4;
 pub const PRIO_DARWIN_BG: ::c_int = 0x1000;
 pub const PRIO_DARWIN_NONUI: ::c_int = 0x1001;
 
+pub const SEM_FAILED: *mut sem_t = -1isize as *mut ::sem_t;
+
 f! {
     pub fn WSTOPSIG(status: ::c_int) -> ::c_int {
         status >> 8
diff --git a/src/unix/bsd/freebsdlike/mod.rs b/src/unix/bsd/freebsdlike/mod.rs
index a265051f0ad736d4f0ee5147542975a0afa02025..2d45af90c06c04e01de4485c41e8aec51655ebfe 100644
--- a/src/unix/bsd/freebsdlike/mod.rs
+++ b/src/unix/bsd/freebsdlike/mod.rs
@@ -11,6 +11,7 @@ pub type tcflag_t = ::c_uint;
 pub type speed_t = ::c_uint;
 pub type nl_item = ::c_int;
 pub type id_t = i64;
+pub type sem_t = _sem;
 
 pub enum timezone {}
 
@@ -149,6 +150,11 @@ s! {
         pub int_p_sign_posn: ::c_char,
         pub int_n_sign_posn: ::c_char,
     }
+
+    // internal structure has changed over time
+    pub struct _sem {
+        data: [u32; 4],
+    }
 }
 
 pub const LC_COLLATE_MASK: ::c_int = (1 << 0);
@@ -677,6 +683,8 @@ pub const LOG_NFACILITIES: ::c_int = 24;
 pub const TIOCGWINSZ: ::c_ulong = 0x40087468;
 pub const TIOCSWINSZ: ::c_ulong = 0x80087467;
 
+pub const SEM_FAILED: *mut sem_t = 0 as *mut sem_t;
+
 #[link(name = "util")]
 extern {
     pub fn getnameinfo(sa: *const ::sockaddr,
diff --git a/src/unix/bsd/openbsdlike/mod.rs b/src/unix/bsd/openbsdlike/mod.rs
index 1f864f640cbb181ae30a9ad7d8c0c2a5b748f486..f8ac09d226df8f9c619f7236af541558dbaa8837 100644
--- a/src/unix/bsd/openbsdlike/mod.rs
+++ b/src/unix/bsd/openbsdlike/mod.rs
@@ -11,8 +11,10 @@ pub type tcflag_t = ::c_uint;
 pub type nl_item = c_long;
 pub type clockid_t = ::c_int;
 pub type id_t = ::uint32_t;
+pub type sem_t = *mut sem;
 
 pub enum timezone {}
+pub enum sem {}
 
 s! {
     pub struct sigaction {
@@ -445,6 +447,8 @@ pub const LOG_NFACILITIES: ::c_int = 24;
 
 pub const HW_NCPU: ::c_int = 3;
 
+pub const SEM_FAILED: *mut sem_t = 0 as *mut sem_t;
+
 #[link(name = "util")]
 extern {
     pub fn mincore(addr: *mut ::c_void, len: ::size_t,
diff --git a/src/unix/mod.rs b/src/unix/mod.rs
index cc79c6e2ef7f78a56d80de7be02fab656fe3770a..446038241f5d4f6b9fcc98c522fde4fa5055793d 100644
--- a/src/unix/mod.rs
+++ b/src/unix/mod.rs
@@ -673,6 +673,20 @@ extern {
     pub fn setlocale(category: ::c_int,
                      locale: *const ::c_char) -> *mut ::c_char;
     pub fn localeconv() -> *mut lconv;
+
+    pub fn sem_destroy(sem: *mut sem_t) -> ::c_int;
+    pub fn sem_open(name: *const ::c_char, oflag: ::c_int, ...) -> *mut sem_t;
+    pub fn sem_close(sem: *mut sem_t) -> ::c_int;
+    pub fn sem_unlink(name: *const ::c_char) -> ::c_int;
+    #[cfg_attr(all(target_os = "macos", target_arch = "x86"),
+               link_name = "sem_wait$UNIX2003")]
+    pub fn sem_wait(sem: *mut sem_t) -> ::c_int;
+    pub fn sem_trywait(sem: *mut sem_t) -> ::c_int;
+    pub fn sem_post(sem: *mut sem_t) -> ::c_int;
+    pub fn sem_init(sem: *mut sem_t,
+                    pshared: ::c_int,
+                    value: ::c_uint)
+                    -> ::c_int;
 }
 
 // TODO: get rid of this cfg(not(...))
diff --git a/src/unix/notbsd/android/mod.rs b/src/unix/notbsd/android/mod.rs
index 39059a655970bdd1d20124b680a27ca103cb0d2f..9551c9d20d7e256120d12bf15cdbccb1b4b1c96f 100644
--- a/src/unix/notbsd/android/mod.rs
+++ b/src/unix/notbsd/android/mod.rs
@@ -95,6 +95,10 @@ s! {
         #[cfg(target_pointer_width = "32")]
         __bits: [__CPU_BITTYPE; 1],
     }
+
+    pub struct sem_t {
+        count: ::c_uint,
+    }
 }
 
 pub const BUFSIZ: ::c_uint = 1024;
@@ -472,6 +476,8 @@ pub const RTLD_NOLOAD: ::c_int = 0x4;
 pub const RTLD_NOW: ::c_int = 0;
 pub const RTLD_DEFAULT: *mut ::c_void = -1isize as *mut ::c_void;
 
+pub const SEM_FAILED: *mut sem_t = 0 as *mut sem_t;
+
 f! {
     pub fn sigemptyset(set: *mut sigset_t) -> ::c_int {
         *set = 0;
diff --git a/src/unix/notbsd/linux/mips.rs b/src/unix/notbsd/linux/mips.rs
index bdead3160de12c54f80723c13c17598e249e9700..fa5a2a36694b118d2363ff51367702f172b0f327 100644
--- a/src/unix/notbsd/linux/mips.rs
+++ b/src/unix/notbsd/linux/mips.rs
@@ -189,6 +189,15 @@ s! {
         pub mem_unit: ::c_uint,
         pub _f: [::c_char; 8],
     }
+
+    // FIXME this is actually a union
+    pub struct sem_t {
+        #[cfg(target_pointer_width = "32")]
+        __size: [::c_char; 16],
+        #[cfg(target_pointer_width = "64")]
+        __size: [::c_char; 32],
+        __align: [::c_long; 0],
+    }
 }
 
 pub const BUFSIZ: ::c_uint = 8192;
diff --git a/src/unix/notbsd/linux/mod.rs b/src/unix/notbsd/linux/mod.rs
index 568f2a463e2e43629aa172b7f3b543c259a0e422..33f2e7ae2ed6839561fac4d701fc70df8474dfaa 100644
--- a/src/unix/notbsd/linux/mod.rs
+++ b/src/unix/notbsd/linux/mod.rs
@@ -452,6 +452,8 @@ pub const AF_NETLINK: ::c_int = 16;
 
 pub const LOG_NFACILITIES: ::c_int = 24;
 
+pub const SEM_FAILED: *mut ::sem_t = 0 as *mut sem_t;
+
 f! {
     pub fn CPU_ZERO(cpuset: &mut cpu_set_t) -> () {
         for slot in cpuset.bits.iter_mut() {
diff --git a/src/unix/notbsd/linux/musl/b32/mod.rs b/src/unix/notbsd/linux/musl/b32/mod.rs
index ad74e8816d17b7f4547cc40d011117b4fde5bf2d..6ae90bd04eb93675c4fae8923285aa3817624dd8 100644
--- a/src/unix/notbsd/linux/musl/b32/mod.rs
+++ b/src/unix/notbsd/linux/musl/b32/mod.rs
@@ -20,6 +20,10 @@ s! {
         pub msg_controllen: ::socklen_t,
         pub msg_flags: ::c_int,
     }
+
+    pub struct sem_t {
+        __val: [::c_int; 4],
+    }
 }
 
 pub const __SIZEOF_PTHREAD_RWLOCK_T: usize = 32;
diff --git a/src/unix/notbsd/linux/musl/b64/mod.rs b/src/unix/notbsd/linux/musl/b64/mod.rs
index a63aa9bcb2e67ad5aae767fa6e0032d7ba2dc16a..0501c553c3478c341e97f9e22ab243d6184bc6ac 100644
--- a/src/unix/notbsd/linux/musl/b64/mod.rs
+++ b/src/unix/notbsd/linux/musl/b64/mod.rs
@@ -100,6 +100,10 @@ s! {
         __pad2: ::socklen_t,
         pub msg_flags: ::c_int,
     }
+
+    pub struct sem_t {
+        __val: [::c_int; 8],
+    }
 }
 
 pub const __SIZEOF_PTHREAD_RWLOCK_T: usize = 56;
diff --git a/src/unix/notbsd/linux/other/mod.rs b/src/unix/notbsd/linux/other/mod.rs
index a9cb0ad6f0f7d04cc27c07c52913e6bf65435779..6ecfe3ccaaff9c1d3bbea357918361d1896d2f3c 100644
--- a/src/unix/notbsd/linux/other/mod.rs
+++ b/src/unix/notbsd/linux/other/mod.rs
@@ -121,6 +121,15 @@ s! {
         __unused4: ::c_ulong,
         __unused5: ::c_ulong
     }
+
+    // FIXME this is actually a union
+    pub struct sem_t {
+        #[cfg(target_pointer_width = "32")]
+        __size: [::c_char; 16],
+        #[cfg(target_pointer_width = "64")]
+        __size: [::c_char; 32],
+        __align: [::c_long; 0],
+    }
 }
 
 pub const RLIMIT_RSS: ::c_int = 5;