void ipc_thread_terminate( thread_t thread) { ipc_port_t kport = thread->ith_self; if (kport != IP_NULL) { int i; if (IP_VALID(thread->ith_sself)) ipc_port_release_send(thread->ith_sself); thread->ith_sself = thread->ith_self = IP_NULL; for (i = FIRST_EXCEPTION; i < EXC_TYPES_COUNT; ++i) { if (IP_VALID(thread->exc_actions[i].port)) ipc_port_release_send(thread->exc_actions[i].port); } ipc_port_dealloc_kernel(kport); } assert(ipc_kmsg_queue_empty(&thread->ith_messages)); if (thread->ith_rpc_reply != IP_NULL) ipc_port_dealloc_reply(thread->ith_rpc_reply); thread->ith_rpc_reply = IP_NULL; }
void ipc_thread_terminate(thread_t thread) { ipc_port_t kport; ith_lock(thread); kport = thread->ith_self; if (kport == IP_NULL) { /* the thread is already terminated (can this happen?) */ ith_unlock(thread); return; } thread->ith_self = IP_NULL; ith_unlock(thread); assert(ipc_kmsg_queue_empty(&thread->ith_messages)); /* release the naked send rights */ if (IP_VALID(thread->ith_sself)) ipc_port_release_send(thread->ith_sself); if (IP_VALID(thread->ith_exception)) ipc_port_release_send(thread->ith_exception); /* destroy the kernel port */ ipc_port_dealloc_kernel(kport); }
void ipc_pset_add( ipc_pset_t pset, ipc_port_t port) { assert(ips_active(pset)); assert(ip_active(port)); assert(port->ip_pset == IPS_NULL); port->ip_pset = pset; port->ip_cur_target = &pset->ips_target; ips_reference(pset); imq_lock(&port->ip_messages); imq_lock(&pset->ips_messages); /* move messages from port's queue to the port set's queue */ ipc_mqueue_move(&pset->ips_messages, &port->ip_messages, port); imq_unlock(&pset->ips_messages); assert(ipc_kmsg_queue_empty(&port->ip_messages.imq_messages)); /* wake up threads waiting to receive from the port */ ipc_mqueue_changed(&port->ip_messages, MACH_RCV_PORT_CHANGED); assert(ipc_thread_queue_empty(&port->ip_messages.imq_threads)); imq_unlock(&port->ip_messages); }
void ipc_mqueue_move( ipc_mqueue_t dest, ipc_mqueue_t source, const ipc_port_t port) { ipc_kmsg_queue_t oldq, newq; ipc_thread_queue_t blockedq; ipc_kmsg_t kmsg, next; ipc_thread_t th; oldq = &source->imq_messages; newq = &dest->imq_messages; blockedq = &dest->imq_threads; for (kmsg = ipc_kmsg_queue_first(oldq); kmsg != IKM_NULL; kmsg = next) { next = ipc_kmsg_queue_next(oldq, kmsg); /* only move messages sent to port */ if (kmsg->ikm_header.msgh_remote_port != (mach_port_t) port) continue; ipc_kmsg_rmqueue(oldq, kmsg); /* before adding kmsg to newq, check for a blocked receiver */ while ((th = ipc_thread_dequeue(blockedq)) != ITH_NULL) { assert(ipc_kmsg_queue_empty(newq)); thread_go(th); /* check if the receiver can handle the message */ if (kmsg->ikm_header.msgh_size <= th->ith_msize) { th->ith_state = MACH_MSG_SUCCESS; th->ith_kmsg = kmsg; th->ith_seqno = port->ip_seqno++; goto next_kmsg; } th->ith_state = MACH_RCV_TOO_LARGE; th->ith_msize = kmsg->ikm_header.msgh_size; } /* didn't find a receiver to handle the message */ ipc_kmsg_enqueue(newq, kmsg); next_kmsg:; } }
void ipc_mqueue_move( ipc_mqueue_t dest, ipc_mqueue_t source, ipc_port_t port) { ipc_kmsg_queue_t oldq, newq; ipc_thread_queue_t blockedq; ipc_kmsg_t kmsg, next; ipc_thread_t th; oldq = &source->imq_messages; newq = &dest->imq_messages; blockedq = &dest->imq_threads; for (kmsg = ipc_kmsg_queue_first(oldq); kmsg != IKM_NULL; kmsg = next) { next = ipc_kmsg_queue_next(oldq, kmsg); /* only move messages sent to port */ if (kmsg->ikm_header.msgh_remote_port != (mach_port_t) port) continue; ipc_kmsg_rmqueue(oldq, kmsg); /* before adding kmsg to newq, check for a blocked receiver */ if ((th = ipc_thread_dequeue(blockedq)) != ITH_NULL) { assert(ipc_kmsg_queue_empty(newq)); /* * Got a receiver. Hand the receiver the message * and process the next one. */ th->ith_state = MACH_MSG_SUCCESS; th->ith_kmsg = kmsg; th->ith_seqno = port->ip_seqno++; thread_go(th); } else { /* didn't find a receiver; enqueue the message */ ipc_kmsg_enqueue(newq, kmsg); } } }
/* * Routine: ipc_mqueue_release_msgcount * Purpose: * Release a message queue reference in the case where we * found a waiter. * * Conditions: * The message queue is locked. * The message corresponding to this reference is off the queue. */ void ipc_mqueue_release_msgcount( ipc_mqueue_t mqueue) { assert(imq_held(mqueue)); assert(mqueue->imq_msgcount > 1 || ipc_kmsg_queue_empty(&mqueue->imq_messages)); mqueue->imq_msgcount--; if (!imq_full(mqueue) && mqueue->imq_fullwaiters) { if (wait_queue_wakeup64_one_locked( &mqueue->imq_wait_queue, IPC_MQUEUE_FULL, THREAD_AWAKENED, FALSE) != KERN_SUCCESS) { mqueue->imq_fullwaiters = FALSE; } else { /* gave away our slot - add reference back */ mqueue->imq_msgcount++; } } }
void ipc_thread_terminate( thread_t thread) { ipc_port_t kport = thread->ith_self; if (kport != IP_NULL) { int i; if (IP_VALID(thread->ith_sself)) ipc_port_release_send(thread->ith_sself); thread->ith_sself = thread->ith_self = IP_NULL; if (thread->exc_actions != NULL) { for (i = FIRST_EXCEPTION; i < EXC_TYPES_COUNT; ++i) { if (IP_VALID(thread->exc_actions[i].port)) ipc_port_release_send(thread->exc_actions[i].port); } ipc_thread_destroy_exc_actions(thread); } ipc_port_dealloc_kernel(kport); } #if IMPORTANCE_INHERITANCE assert(thread->ith_assertions == 0); #endif assert(ipc_kmsg_queue_empty(&thread->ith_messages)); if (thread->ith_rpc_reply != IP_NULL) ipc_port_dealloc_reply(thread->ith_rpc_reply); thread->ith_rpc_reply = IP_NULL; }
void exception_raise( ipc_port_t dest_port, ipc_port_t thread_port, ipc_port_t task_port, integer_t _exception, integer_t code, integer_t subcode) { ipc_thread_t self = current_thread(); ipc_thread_t receiver; ipc_port_t reply_port; ipc_mqueue_t dest_mqueue; ipc_mqueue_t reply_mqueue; ipc_kmsg_t kmsg; mach_msg_return_t mr; assert(IP_VALID(dest_port)); /* * We will eventually need a message buffer. * Grab the buffer now, while nothing is locked. * This buffer will get handed to the exception server, * and it will give the buffer back with its reply. */ kmsg = ikm_cache(); if (kmsg != IKM_NULL) { ikm_cache() = IKM_NULL; ikm_check_initialized(kmsg, IKM_SAVED_KMSG_SIZE); } else { kmsg = ikm_alloc(IKM_SAVED_MSG_SIZE); if (kmsg == IKM_NULL) panic("exception_raise"); ikm_init(kmsg, IKM_SAVED_MSG_SIZE); } /* * We need a reply port for the RPC. * Check first for a cached port. */ ith_lock(self); assert(self->ith_self != IP_NULL); reply_port = self->ith_rpc_reply; if (reply_port == IP_NULL) { ith_unlock(self); reply_port = ipc_port_alloc_reply(); ith_lock(self); if ((reply_port == IP_NULL) || (self->ith_rpc_reply != IP_NULL)) panic("exception_raise"); self->ith_rpc_reply = reply_port; } ip_lock(reply_port); assert(ip_active(reply_port)); ith_unlock(self); /* * Make a naked send-once right for the reply port, * to hand to the exception server. * Make an extra reference for the reply port, * to receive on. This protects us against * mach_msg_abort_rpc. */ reply_port->ip_sorights++; ip_reference(reply_port); ip_reference(reply_port); self->ith_port = reply_port; reply_mqueue = &reply_port->ip_messages; imq_lock(reply_mqueue); assert(ipc_kmsg_queue_empty(&reply_mqueue->imq_messages)); ip_unlock(reply_port); /* * Make sure we can queue to the destination port. */ if (!ip_lock_try(dest_port)) { imq_unlock(reply_mqueue); goto slow_exception_raise; } if (!ip_active(dest_port) || (dest_port->ip_receiver == ipc_space_kernel)) { imq_unlock(reply_mqueue); ip_unlock(dest_port); goto slow_exception_raise; } /* * Find the destination message queue. */ { ipc_pset_t dest_pset; dest_pset = dest_port->ip_pset; if (dest_pset == IPS_NULL) dest_mqueue = &dest_port->ip_messages; else dest_mqueue = &dest_pset->ips_messages; } if (!imq_lock_try(dest_mqueue)) { imq_unlock(reply_mqueue); ip_unlock(dest_port); goto slow_exception_raise; } /* * Safe to unlock dest_port, because we hold * dest_mqueue locked. We never bother changing * dest_port->ip_msgcount. */ ip_unlock(dest_port); receiver = ipc_thread_queue_first(&dest_mqueue->imq_threads); if ((receiver == ITH_NULL) || !((receiver->swap_func == (void (*)()) mach_msg_continue) || ((receiver->swap_func == (void (*)()) mach_msg_receive_continue) && (sizeof(struct mach_exception) <= receiver->ith_msize) && ((receiver->ith_option & MACH_RCV_NOTIFY) == 0))) || !thread_handoff(self, exception_raise_continue, receiver)) { imq_unlock(reply_mqueue); imq_unlock(dest_mqueue); goto slow_exception_raise; } counter(c_exception_raise_block++); assert(current_thread() == receiver); /* * We need to finish preparing self for its * time asleep in reply_mqueue. self is left * holding the extra ref for reply_port. */ ipc_thread_enqueue_macro(&reply_mqueue->imq_threads, self); self->ith_state = MACH_RCV_IN_PROGRESS; self->ith_msize = MACH_MSG_SIZE_MAX; imq_unlock(reply_mqueue); /* * Finish extracting receiver from dest_mqueue. */ ipc_thread_rmqueue_first_macro( &dest_mqueue->imq_threads, receiver); imq_unlock(dest_mqueue); /* * Release the receiver's reference for his object. */ { ipc_object_t object = receiver->ith_object; io_lock(object); io_release(object); io_check_unlock(object); } { struct mach_exception *exc = (struct mach_exception *) &kmsg->ikm_header; ipc_space_t space = receiver->task->itk_space; /* * We are running as the receiver now. We hold * the following resources, which must be consumed: * kmsg, send-once right for reply_port * send rights for dest_port, thread_port, task_port * Synthesize a kmsg for copyout to the receiver. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_size = sizeof *exc; /* exc->Head.msgh_remote_port later */ /* exc->Head.msgh_local_port later */ exc->Head.msgh_seqno = 0; exc->Head.msgh_id = MACH_EXCEPTION_ID; exc->threadType = exc_port_proto; /* exc->thread later */ exc->taskType = exc_port_proto; /* exc->task later */ exc->exceptionType = exc_code_proto; exc->exception = _exception; exc->codeType = exc_code_proto; exc->code = code; exc->subcodeType = exc_code_proto; exc->subcode = subcode; /* * Check that the receiver can handle the message. */ if (receiver->ith_rcv_size < sizeof(struct mach_exception)) { /* * ipc_kmsg_destroy is a handy way to consume * the resources we hold, but it requires setup. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_remote_port = (mach_port_t) dest_port; exc->Head.msgh_local_port = (mach_port_t) reply_port; exc->thread = (mach_port_t) thread_port; exc->task = (mach_port_t) task_port; ipc_kmsg_destroy(kmsg); thread_syscall_return(MACH_RCV_TOO_LARGE); /*NOTREACHED*/ } is_write_lock(space); assert(space->is_active); /* * To do an atomic copyout, need simultaneous * locks on both ports and the space. */ ip_lock(dest_port); if (!ip_active(dest_port) || !ip_lock_try(reply_port)) { abort_copyout: ip_unlock(dest_port); is_write_unlock(space); /* * Oh well, we have to do the header the slow way. * First make it look like it's in-transit. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_remote_port = (mach_port_t) dest_port; exc->Head.msgh_local_port = (mach_port_t) reply_port; mr = ipc_kmsg_copyout_header(&exc->Head, space, MACH_PORT_NULL); if (mr == MACH_MSG_SUCCESS) goto copyout_body; /* * Ack! Prepare for ipc_kmsg_copyout_dest. * It will consume thread_port and task_port. */ exc->thread = (mach_port_t) thread_port; exc->task = (mach_port_t) task_port; ipc_kmsg_copyout_dest(kmsg, space); (void) ipc_kmsg_put(receiver->ith_msg, kmsg, sizeof(mach_msg_header_t)); thread_syscall_return(mr); /*NOTREACHED*/ } if (!ip_active(reply_port)) { ip_unlock(reply_port); goto abort_copyout; } assert(reply_port->ip_sorights > 0); ip_unlock(reply_port); { kern_return_t kr; ipc_entry_t entry; kr = ipc_entry_get (space, &exc->Head.msgh_remote_port, &entry); if (kr) goto abort_copyout; { mach_port_gen_t gen; assert((entry->ie_bits &~ IE_BITS_GEN_MASK) == 0); gen = entry->ie_bits + IE_BITS_GEN_ONE; /* optimized ipc_right_copyout */ entry->ie_bits = gen | (MACH_PORT_TYPE_SEND_ONCE | 1); } entry->ie_object = (ipc_object_t) reply_port; is_write_unlock(space); } /* optimized ipc_object_copyout_dest */ assert(dest_port->ip_srights > 0); ip_release(dest_port); exc->Head.msgh_local_port = ((dest_port->ip_receiver == space) ? dest_port->ip_receiver_name : MACH_PORT_NULL); if ((--dest_port->ip_srights == 0) && (dest_port->ip_nsrequest != IP_NULL)) { ipc_port_t nsrequest; mach_port_mscount_t mscount; /* a rather rare case */ nsrequest = dest_port->ip_nsrequest; mscount = dest_port->ip_mscount; dest_port->ip_nsrequest = IP_NULL; ip_unlock(dest_port); ipc_notify_no_senders(nsrequest, mscount); } else ip_unlock(dest_port); copyout_body: /* * Optimized version of ipc_kmsg_copyout_body, * to handle the two ports in the body. */ mr = (ipc_kmsg_copyout_object(space, (ipc_object_t) thread_port, MACH_MSG_TYPE_PORT_SEND, &exc->thread) | ipc_kmsg_copyout_object(space, (ipc_object_t) task_port, MACH_MSG_TYPE_PORT_SEND, &exc->task)); if (mr != MACH_MSG_SUCCESS) { (void) ipc_kmsg_put(receiver->ith_msg, kmsg, kmsg->ikm_header.msgh_size); thread_syscall_return(mr | MACH_RCV_BODY_ERROR); /*NOTREACHED*/ } } /* * Optimized version of ipc_kmsg_put. * We must check ikm_cache after copyoutmsg. */ ikm_check_initialized(kmsg, kmsg->ikm_size); assert(kmsg->ikm_size == IKM_SAVED_KMSG_SIZE); if (copyoutmsg(&kmsg->ikm_header, receiver->ith_msg, sizeof(struct mach_exception)) || (ikm_cache() != IKM_NULL)) { mr = ipc_kmsg_put(receiver->ith_msg, kmsg, kmsg->ikm_header.msgh_size); thread_syscall_return(mr); /*NOTREACHED*/ } ikm_cache() = kmsg; thread_syscall_return(MACH_MSG_SUCCESS); /*NOTREACHED*/ #ifndef __GNUC__ return; /* help for the compiler */ #endif slow_exception_raise: { struct mach_exception *exc = (struct mach_exception *) &kmsg->ikm_header; ipc_kmsg_t reply_kmsg; mach_port_seqno_t reply_seqno; exception_raise_misses++; /* * We hold the following resources, which must be consumed: * kmsg, send-once right and ref for reply_port * send rights for dest_port, thread_port, task_port * Synthesize a kmsg to send. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_size = sizeof *exc; exc->Head.msgh_remote_port = (mach_port_t) dest_port; exc->Head.msgh_local_port = (mach_port_t) reply_port; exc->Head.msgh_seqno = 0; exc->Head.msgh_id = MACH_EXCEPTION_ID; exc->threadType = exc_port_proto; exc->thread = (mach_port_t) thread_port; exc->taskType = exc_port_proto; exc->task = (mach_port_t) task_port; exc->exceptionType = exc_code_proto; exc->exception = _exception; exc->codeType = exc_code_proto; exc->code = code; exc->subcodeType = exc_code_proto; exc->subcode = subcode; ipc_mqueue_send_always(kmsg); /* * We are left with a ref for reply_port, * which we use to receive the reply message. */ ip_lock(reply_port); if (!ip_active(reply_port)) { ip_unlock(reply_port); exception_raise_continue_slow(MACH_RCV_PORT_DIED, IKM_NULL, /*dummy*/0); /*NOTREACHED*/ } imq_lock(reply_mqueue); ip_unlock(reply_port); mr = ipc_mqueue_receive(reply_mqueue, MACH_MSG_OPTION_NONE, MACH_MSG_SIZE_MAX, MACH_MSG_TIMEOUT_NONE, FALSE, exception_raise_continue, &reply_kmsg, &reply_seqno); /* reply_mqueue is unlocked */ exception_raise_continue_slow(mr, reply_kmsg, reply_seqno); /*NOTREACHED*/ } }
mach_msg_return_t ipc_mqueue_send( ipc_kmsg_t kmsg, mach_msg_option_t option, mach_msg_timeout_t time_out) { ipc_port_t port; port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; assert(IP_VALID(port)); ip_lock(port); if (port->ip_receiver == ipc_space_kernel) { ipc_kmsg_t reply; /* * We can check ip_receiver == ipc_space_kernel * before checking that the port is active because * ipc_port_dealloc_kernel clears ip_receiver * before destroying a kernel port. */ assert(ip_active(port)); ip_unlock(port); reply = ipc_kobject_server(kmsg); if (reply != IKM_NULL) ipc_mqueue_send_always(reply); return MACH_MSG_SUCCESS; } for (;;) { ipc_thread_t self; /* * Can't deliver to a dead port. * However, we can pretend it got sent * and was then immediately destroyed. */ if (!ip_active(port)) { /* * We can't let ipc_kmsg_destroy deallocate * the port right, because we might end up * in an infinite loop trying to deliver * a send-once notification. */ ip_release(port); ip_check_unlock(port); kmsg->ikm_header.msgh_remote_port = MACH_PORT_NULL; ipc_kmsg_destroy(kmsg); return MACH_MSG_SUCCESS; } /* * Don't block if: * 1) We're under the queue limit. * 2) Caller used the MACH_SEND_ALWAYS internal option. * 3) Message is sent to a send-once right. */ if ((port->ip_msgcount < port->ip_qlimit) || (option & MACH_SEND_ALWAYS) || (MACH_MSGH_BITS_REMOTE(kmsg->ikm_header.msgh_bits) == MACH_MSG_TYPE_PORT_SEND_ONCE)) break; /* must block waiting for queue to clear */ self = current_thread(); if (option & MACH_SEND_TIMEOUT) { if (time_out == 0) { ip_unlock(port); return MACH_SEND_TIMED_OUT; } thread_will_wait_with_timeout(self, time_out); } else thread_will_wait(self); ipc_thread_enqueue(&port->ip_blocked, self); self->ith_state = MACH_SEND_IN_PROGRESS; ip_unlock(port); counter(c_ipc_mqueue_send_block++); thread_block((void (*)(void)) 0); ip_lock(port); /* why did we wake up? */ if (self->ith_state == MACH_MSG_SUCCESS) continue; assert(self->ith_state == MACH_SEND_IN_PROGRESS); /* take ourselves off blocked queue */ ipc_thread_rmqueue(&port->ip_blocked, self); /* * Thread wakeup-reason field tells us why * the wait was interrupted. */ switch (self->ith_wait_result) { case THREAD_INTERRUPTED: /* send was interrupted - give up */ ip_unlock(port); return MACH_SEND_INTERRUPTED; case THREAD_TIMED_OUT: /* timeout expired */ assert(option & MACH_SEND_TIMEOUT); time_out = 0; break; case THREAD_RESTART: default: #if MACH_ASSERT assert(!"ipc_mqueue_send"); #else panic("ipc_mqueue_send"); #endif } } if (kmsg->ikm_header.msgh_bits & MACH_MSGH_BITS_CIRCULAR) { ip_unlock(port); /* don't allow the creation of a circular loop */ ipc_kmsg_destroy(kmsg); return MACH_MSG_SUCCESS; } { ipc_mqueue_t mqueue; ipc_pset_t pset; ipc_thread_t receiver; ipc_thread_queue_t receivers; port->ip_msgcount++; assert(port->ip_msgcount > 0); pset = port->ip_pset; if (pset == IPS_NULL) mqueue = &port->ip_messages; else mqueue = &pset->ips_messages; imq_lock(mqueue); receivers = &mqueue->imq_threads; /* * Can unlock the port now that the msg queue is locked * and we know the port is active. While the msg queue * is locked, we have control of the kmsg, so the ref in * it for the port is still good. If the msg queue is in * a set (dead or alive), then we're OK because the port * is still a member of the set and the set won't go away * until the port is taken out, which tries to lock the * set's msg queue to remove the port's msgs. */ ip_unlock(port); /* check for a receiver for the message */ for (;;) { receiver = ipc_thread_queue_first(receivers); if (receiver == ITH_NULL) { /* no receivers; queue kmsg */ ipc_kmsg_enqueue_macro(&mqueue->imq_messages, kmsg); imq_unlock(mqueue); break; } ipc_thread_rmqueue_first_macro(receivers, receiver); assert(ipc_kmsg_queue_empty(&mqueue->imq_messages)); if (kmsg->ikm_header.msgh_size <= receiver->ith_msize) { /* got a successful receiver */ receiver->ith_state = MACH_MSG_SUCCESS; receiver->ith_kmsg = kmsg; receiver->ith_seqno = port->ip_seqno++; imq_unlock(mqueue); thread_go(receiver); break; } receiver->ith_state = MACH_RCV_TOO_LARGE; receiver->ith_msize = kmsg->ikm_header.msgh_size; thread_go(receiver); } } current_task()->messages_sent++; return MACH_MSG_SUCCESS; }