//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: EndpointCore.cs // // HACK: Because we currently compile this file as part of the Kernel with C#, we can't // make EndpointCore a rep struct, which is necessary for inheriting from it. // The compiler and Bartok recognize EndpointCore as special and treat it as // unsealed. // This hack can be removed, once we compile it with Sing#. // DON'T change the name until then! using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Diagnostics; using Microsoft.Singularity.Channels; using Microsoft.Singularity.Security; using Microsoft.Singularity.Memory; namespace Microsoft.Singularity.Channels { using Microsoft.Singularity.V1.Threads; using Microsoft.Singularity.V1.Security; using Microsoft.Singularity.V1.Services; using Microsoft.Singularity.V1.Types; using Allocation = Microsoft.Singularity.Memory.SharedHeap.Allocation; using System.Threading; using SharedHeap = Microsoft.Singularity.Memory.SharedHeap; [CLSCompliant(false)] public enum EndpointCoreEvent : ushort { Connect = 4, TransferToProcess = 5, } #if !PAGING [CLSCompliant(false)] [CCtorIsRunDuringStartup] unsafe public struct EndpointCore #else // The portion of the endpoint that is only // accessible to the kernel or to trusted run-time code: [CLSCompliant(false)] unsafe struct EndpointTrusted #endif { /// /// Flag indicating that the endpoint is closed. /// This gets set either by an explicit close, or when /// the kernel determines that the endpoint is not reachable. /// NOTE: A channel only goes away entirely, once both ends are closed! /// /// NOTE: The lifetime of the various involved data structures is /// as follows: /// /// Endpoint Allocations: as today. Owned by a process, goes away when /// the process decides to free it or the /// sharedHeapWalker finds it. /// /// Endpoint structures: ref counted as today in a process domain. /// Alive as long as there is at least one allocation /// structure to it. Note that if the channel spans /// protection domains, then the endpoint structure is /// not kept alive by the peer. /// /// Endpoint trusted: ref counted in the kernel. Get collected once both /// sides are closed. /// private volatile bool closed; /// /// The endpoint to which this endpoint is connected. /// #if PAGING private EndpointTrusted* peer; static private SpinLock spinLock; private int refCount; /// /// The user-level allocation for the endpoint that owns this /// EndpointCoreData struct. /// this == this->self->data->trusted /// private Allocation* /*EndpointCore* opt(ExHeap)*/ self; /// /// If the peer is in the same protection domain, then this /// points to an allocation owned by this side to the peer /// EndpointCoreData struct. /// /// We materialize this on demand as well as tear it down /// when an endpoint moves out of a protection domain. /// /// This allocation is used for the fast path to write directly /// into the peer data structure. /// internal Allocation* /*EndpointCore* opt(ExHeap)*/ directPeerAllocation; private EndpointUpdates endpointUpdates; /// /// Return a handle for the peer /// internal Allocation* DirectPeerAllocation(out bool marshall) { marshall = ChannelSpansDomains; return this.directPeerAllocation; } bool ChannelSpansDomains { get { Process us = Process.GetProcessByID(this.ownerProcessId); Process them = Process.GetProcessByID(this.peer->ownerProcessId); return (us.ProcessSharedHeap != them.ProcessSharedHeap); } } #else private Allocation* /*EndpointCore* opt(ExHeap)*/ peer; #endif /// /// Event on which sends are signaled to this endpoint. /// The handle is owned by the kernel, since the endpoint can move. /// The kernel deallocates the handle when the channel is deallocated. /// NOTE: stays valid until the entire channel gets collected. /// private AutoResetEventHandle messageEvent; /// /// Event handle in case this endpoint is part of a collection /// private AutoResetEventHandle collectionEvent; /// /// Contains the process id of the process currently owning this end of the /// channel. /// private int ownerProcessId; /// /// Contains the principal that currently controls this end of the /// channel. For now, this will simply be the owning process ID. But /// this will change once we implement delegation. /// private PrincipalHandle ownerPrincipalHandle; /// /// Contains the number of sends to this endpoint. /// private int receiveCount; /// /// Contains the channelId (positive on the EXP endpoint, negative on the imp endpoint) /// private int channelId; /// /// Contains the pinned status /// ///private bool pinned; private static int channelIdGenerator; private static int openChannelCount = 0; #if SINGULARITY_KERNEL && CHANNEL_COUNT internal static void IncreaseBytesSentCount(long bytes) { PerfCounters.AddBytesSent(bytes); } #endif // SINGULARITY_KERNEL public static int OpenChannelCount { get { return openChannelCount; } } #if PAGING struct FreeElement { internal FreeElement* next; } private static FreeElement* freeAllocList; private static SpinLock allocLock; // Warning: this allocation resides in a user address space. // TODO: add sanity checks to ensure the correct address space is mapped. public Allocation* Self { get { return self; } } public EndpointTrusted* Peer { [NoHeapAllocation] get { return peer; } } public void FlushTo(Allocation* peer) { // only touch peer and kernel this.endpointUpdates.Fetch(peer); } public static EndpointTrusted* New(Allocation* /*EndpointCore* opt(ExHeap)!*/ self) { bool iflag = Processor.DisableInterrupts(); allocLock.Acquire(); try { // // Attempt to get a struct from our free list. // FreeElement* address = freeAllocList; if (address != null) { freeAllocList = address->next; } else { // // Free list is empty. // Allocate new page and break it up into structs. // Use first struct to satisfy current // request, put remainder on the free list. // UIntPtr newSpace = MemoryManager.KernelAllocate( 1, null, 0, System.GCs.PageType.Shared); uint size = (uint)sizeof(EndpointTrusted); address = (FreeElement*) newSpace; newSpace += size; uint spaceRemaining = MemoryManager.PageSize - size; while (spaceRemaining > size) { FreeElement* next = freeAllocList; freeAllocList = (FreeElement*) newSpace; freeAllocList->next = next; newSpace += size; spaceRemaining -= size; } } // // Initialize the trusted endpoint (zero fields). // Buffer.ZeroMemory((byte *)address, sizeof(EndpointTrusted)); EndpointTrusted* ep = (EndpointTrusted*) address; ep->refCount = 0; ep->self = self; return ep; } finally { allocLock.Release(); Processor.RestoreInterrupts(iflag); } } public static void Delete(EndpointTrusted* ep) { bool iflag = Processor.DisableInterrupts(); allocLock.Acquire(); try { FreeElement* element = (FreeElement*) ep; element->next = freeAllocList; freeAllocList = element; } finally { allocLock.Release(); Processor.RestoreInterrupts(iflag); } } #endif /// /// Closes this end of the channel and frees associated resources, EXCEPT the block /// of memory for this endpoint. It must be released by the caller. Sing# does this /// for the programmer. /// /// This runs in the kernel to avoid a race condition with Process.Stop. #if !PAGING public bool Dispose() { if (this.Closed()) { return false; } this.Close(); // mark our side closed before waking up peer // (Note: peer may be null if process stopped before Connect.) if (this.peer != null) { EndpointCore* peerData = (EndpointCore*)Allocation.GetDataUnchecked(this.peer); if (peerData == null) { return false; } peerData->Notify(); // wake up peer thread if waiting to receive } return true; } #else public static void Dispose(EndpointTrusted* endpoint) { if (endpoint->Closed()) { throw new ApplicationException("Endpoint already closed"); } endpoint->Close(); // mark our side closed before waking up peer // (Note: peer may be null if process stopped before Connect.) if (endpoint->Peer != null) { endpoint->Peer->Notify(); // wake up peer thread if waiting to receive } } #endif //PAGING #if !PAGING /// /// Explicitly frees this end of the channel. If other end is also /// freed, the channel is deallocated, meaning we have to deallocate /// the kernel handles for the auto reset events. /// Since both threads on the channel could try to do this simultaneously, /// we use the ref counting on the underlying endpoints to let the last /// free operation (the one pulling the ref count to 0) to free the associated /// event. /// public static void Free(Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint) { // Use unchecked GetData here, since this may be called from the // cleanup threading running in the kernel. // EndpointCore* epData = (EndpointCore*)Allocation.GetDataUnchecked(endpoint); if (epData == null) throw new ApplicationException("SharedHeap.GetData return null"); if (!epData->closed) { throw new ApplicationException("Endpoint must have been Disposed prior to Delete"); } Tracing.Log(Tracing.Debug, "Freeing endpoint {0:x8}", (UIntPtr)endpoint); // If peer is also closed, try to free ALL auto-reset-event resources associated with the channel. // (Note: peer may be null if process stopped before Connect.) if (epData->peer != null) { // Use unchecked GetData here, since this may be called from the // cleanup threading running in the kernel. // TryFreeResources(epData->peer, SharedHeap.CurrentProcessSharedHeap.EndpointPeerOwnerId); // release our ref count on the peer } TryFreeResources(endpoint, SharedHeap.CurrentProcessSharedHeap.EndpointOwnerId); // release our endpoint } #else /// /// This is initiated by one side's Allocation endpoint being deleted (either explicitly) /// or via the cleanup thread. /// The code here is responsible for /// 1) releasing the direct peer allocation /// 2) decrementing the trusted ref count on the channel /// 3) if the trusted ref count goes to 0 for one of the two trusted endpoints, /// then delete that trusted endpoint and its associated structures. /// /// NOTE: the main allocation and endpoint structures in the exchange heap are /// not deleted here! /// public static void Free(EndpointTrusted* endpoint) { if (!endpoint->closed) { throw new ApplicationException("Endpoint must have been Disposed prior to Delete"); } Tracing.Log(Tracing.Debug, "Freeing endpoint {0:x8}", (UIntPtr)(endpoint->self)); #region Free the user data endpoint and peer // We assume the freeing is done by a thread that runs in the same // protection domain as the endpoints. Otherwise, how could we possibly // access the data // (Note: peer may be null if process stopped before Connect.) Allocation* directPeerAllocation = endpoint->directPeerAllocation; if (directPeerAllocation != null) { // This means the peer is in the same protection domain. // delete this allocation. // SharedHeap.CurrentProcessSharedHeap.Free(directPeerAllocation, SharedHeap.CurrentProcessSharedHeap.EndpointPeerOwnerId); SharedHeap.CurrentProcessSharedHeap.Free(endpoint->Self, SharedHeap.CurrentProcessSharedHeap.EndpointOwnerId); } #endregion // If peer is also closed, try to free ALL auto-reset-event resources associated with the channel. // (Note: peer may be null if process stopped before Connect.) if (endpoint->Peer != null) { TryFreeResources(endpoint->Peer); // release our ref count on the peer } TryFreeResources(endpoint); // release our endpoint } #endif //PAGING /// /// The peer thread might try this too, so we use the ref count of the underlying memory /// to allow only the last freeer to also free the associated auto-reset event handle. /// Make sure to grab the handle before freeing the endpoint. /// #if !PAGING unsafe private static void TryFreeResources(Allocation* /*EndpointCore*/ endpoint, SharedHeap.AllocationOwnerId ownerId) { // Use unchecked GetData here, since this may be called from the // cleanup threading running in the kernel. // EndpointCore* epData = (EndpointCore*)Allocation.GetDataUnchecked(endpoint); AutoResetEventHandle areHandle = epData->messageEvent; int channelId = epData->channelId; bool lastRefGone = SharedHeap.KernelSharedHeap.Free(endpoint, ownerId); if (lastRefGone && channelId > 0) { // the entire channel is closed which we attribute to the closing of the Exp side // (whose channelId is positive). // DebugStub.Assert(openChannelCount > 0, "EndpointCore.cs: attempt to take open " + "channel count negative"); openChannelCount--; Tracing.Log(Tracing.Debug, "Entire channel is closed {0:x8}", (UIntPtr)endpoint); } if (lastRefGone && areHandle.id != UIntPtr.Zero) { // got the AutoResetEventHandle Process.kernelProcess.ReleaseHandle(areHandle.id); } } #else unsafe private static void TryFreeResources(EndpointTrusted* endpoint) { AutoResetEventHandle areHandle = endpoint->messageEvent; int channelId = endpoint->channelId; bool iflag = Processor.DisableInterrupts(); spinLock.Acquire(); bool lastRefGone; try { endpoint->refCount--; lastRefGone = (endpoint->refCount == 0); } finally { spinLock.Release(); Processor.RestoreInterrupts(iflag); } if (lastRefGone && channelId > 0) { // the entire channel is closed which we attribute to the closing of the Exp side // (whose channelId is positive). // DebugStub.Assert(openChannelCount > 0, "EndpointCore.cs: attempt to take open " + "channel count negative"); openChannelCount--; Tracing.Log(Tracing.Debug, "Entire channel is closed {0:x8}", (UIntPtr)(endpoint->self)); } if (lastRefGone) { endpoint->endpointUpdates.FreeResources(); EndpointTrusted.Delete(endpoint); } if (lastRefGone && areHandle.id != UIntPtr.Zero) { // got the AutoResetEventHandle Process.kernelProcess.ReleaseHandle(areHandle.id); } } #endif //PAGING /// /// Performs the initialization of the core part of each endpoint and cross links /// them to form a channel. /// #if !PAGING public static void Connect( Allocation* /*EndpointCore* opt(ExHeap)!*/ imp, Allocation* /*EndpointCore* opt(ExHeap)!*/ exp) { if (imp == null || exp == null) { throw new ApplicationException("Connect called with null endpoints"); } Tracing.Log(Tracing.Debug, "connect {0:x8} and {1:x8}", (UIntPtr)imp, (UIntPtr)exp); // keep track of how many channels are open openChannelCount++; #if CHANNEL_COUNT PerfCounters.IncrementChannelsCreated(); #endif EndpointCore* impData = (EndpointCore*)Allocation.GetData(imp); EndpointCore* expData = (EndpointCore*)Allocation.GetData(exp); if (impData == null || expData == null) { throw new ApplicationException("SharedHeap.GetData return null"); } impData->Initialize(exp); expData->Initialize(imp); expData->channelId = ++channelIdGenerator; impData->channelId = -1 * channelIdGenerator; Monitoring.Log(Monitoring.Provider.EndpointCore, (ushort)EndpointCoreEvent.Connect, 0, (uint)expData->channelId, 0, 0, 0, 0); } #else public static void Connect( EndpointTrusted* imp, EndpointTrusted* exp, Allocation* /*EndpointCore* opt(ExHeap)!*/ userImp, Allocation* /*EndpointCore* opt(ExHeap)!*/ userExp) { if (imp == null || exp == null) { throw new ApplicationException("Connect called with null endpoints"); } Tracing.Log(Tracing.Debug, "connect {0:x8} and {1:x8}", (UIntPtr)(imp->self), (UIntPtr)(exp->self)); // keep track of how many channels are open openChannelCount++; imp->Initialize(exp, userExp); exp->Initialize(imp, userImp); exp->channelId = ++channelIdGenerator; imp->channelId = -1 * channelIdGenerator; Monitoring.Log(Monitoring.Provider.EndpointCore, (ushort)EndpointCoreEvent.Connect, 0, (uint)exp->channelId, 0, 0, 0, 0); } #endif //PAGING #if !PAGING private void Initialize(Allocation* /*EndpointCore* opt(ExHeap)*/ myPeer) #else // Careful: it seems that the allocator for trusted endpoints does not null // memory! private void Initialize(EndpointTrusted* myPeer, Allocation* /*EndpointCore* opt(ExHeap)*/ myUserPeer) #endif { // // Create a new auto-reset event, and a handle in the kernel // process to hold it. // this.messageEvent = new AutoResetEventHandle( Process.kernelProcess.AllocateHandle(new AutoResetEvent(false))); this.collectionEvent = new AutoResetEventHandle(); this.ownerProcessId = Thread.CurrentProcess.ProcessId; this.ownerPrincipalHandle = new PrincipalHandle(Thread.CurrentProcess.Principal.Val); this.closed = false; //this.pinned = false; this.receiveCount = 0; this.channelId = 0; // set in the Connect for Exp // IMPORTANT: Put peer allocations in separate list to avoid putting // endpoints twice in the same list: #if !PAGING this.peer = SharedHeap.CurrentProcessSharedHeap.Share(myPeer, SharedHeap.CurrentProcessSharedHeap.EndpointPeerOwnerId, (UIntPtr)0, Allocation.GetSize(myPeer)); #else this.directPeerAllocation = SharedHeap.CurrentProcessSharedHeap.Share(myUserPeer, SharedHeap.CurrentProcessSharedHeap.EndpointPeerOwnerId, (UIntPtr)0, Allocation.GetSize(myUserPeer)); // IMPORTANT: our update buffer must be as big as the peer endpoint, // not self. endpointUpdates.Initialize(myUserPeer); this.refCount++; this.peer = myPeer; SpinLock peerLock = spinLock; // TODO: remove synchronization here; it looks useless bool iflag = Processor.DisableInterrupts(); peerLock.Acquire(); try { myPeer->refCount++; } finally { peerLock.Release(); Processor.RestoreInterrupts(iflag); } #endif } /// /// Set this end to closed /// public void Close() { this.closed = true; } public bool Closed() { return this.closed; } #if PAGING /// /// The event to wait for messages on this endpoint. Used by Select. /// public SyncHandle GetWaitHandle() { return messageEvent; } #endif //PAGING /// /// Notify the owner of this endpoint that a message is ready. /// Notifies the set owner if this endpoint is part of a set. /// private void Notify() { this.receiveCount++; Tracing.Log(Tracing.Debug, "Endpoint Notify"); // NB maf / Cache the collection event to prevent // a race with the receiver. AutoResetEventHandle cached_collEvent = this.collectionEvent; if (cached_collEvent.id != UIntPtr.Zero) { AutoResetEventHandle.Set(cached_collEvent); } AutoResetEventHandle.Set(this.messageEvent); } internal void NotifyPeer() { // commit the update record if necessary #if PAGING if (ChannelSpansDomains) { this.endpointUpdates.EndUpdate(); } peer->Notify(); #else EndpointCore* peerData = (EndpointCore*)Allocation.GetData(this.peer); peerData->Notify(); #endif #if CHANNEL_COUNT //Interlocked.Increment(ref messageCount); PerfCounters.IncrementMsgsSent(); #endif } #if !PAGING /// /// Used internally by the kernel to transfer an endpoint to a new owner /// Called from TRef's within the Kernel. /// /// The argument ep must be an endpoint. public static Allocation* MoveEndpoint(SharedHeap fromHeap, SharedHeap toHeap, Process newOwner, Allocation* ep) { // Only one heap on non-PAGING builds! DebugStub.Assert(fromHeap == toHeap); // Careful about the order. // Since we don't know if this is a release (current process owns it) // or an acquire (current process does not necessarily own it), we // have to bypass the owner check here. int processId = newOwner.ProcessId; EndpointCore* epData = (EndpointCore*)Allocation.GetDataUnchecked(ep); epData->ownerProcessId = processId; epData->ownerPrincipalHandle = new PrincipalHandle(newOwner.Principal.Val); Allocation.SetOwnerProcessId(ep, processId); Allocation.SetOwnerProcessId(epData->peer, processId); Monitoring.Log(Monitoring.Provider.EndpointCore, (ushort)EndpointCoreEvent.TransferToProcess, 0, (uint)epData->channelId, (uint)processId, 0, 0, 0); return ep; } #else /// /// Only performs the adjustments of the ownership of blocks, /// No copies. /// private static void TransferToProcess(EndpointTrusted* ep, Process p) { int processId = p.ProcessId; ep->ownerProcessId = processId; ep->ownerPrincipalHandle = new PrincipalHandle(p.Principal.Val); // TODO: allocations should be process-local: Allocation.SetOwnerProcessId(ep->self, processId); Allocation.SetOwnerProcessId(ep->directPeerAllocation, processId); Monitoring.Log(Monitoring.Provider.EndpointCore, (ushort)EndpointCoreEvent.TransferToProcess, 0, (uint)ep->channelId, (uint)processId, 0, 0, 0); } /// /// Generic copy (either from kernel or to kernel) /// Determines if the thing we are moving is an endpoint and copies it accordingly. /// private static SystemType EndpointCoreSystemType = typeof(Microsoft.Singularity.V1.Services.EndpointCore).GetSystemType(); public static Allocation* MoveData(SharedHeap fromHeap, SharedHeap toHeap, Process newOwner, Allocation* data) { if (data == null) return data; if (!fromHeap.Validate(data)) { throw new ArgumentException("Bad argument. Not visible"); } // We can only transfer either into our out of the kernel's heap DebugStub.Assert(fromHeap == SharedHeap.KernelSharedHeap || toHeap == SharedHeap.KernelSharedHeap); if (SystemType.IsSubtype(data, EndpointCoreSystemType)) { // we have an endpoint return MoveEndpoint(fromHeap, toHeap, newOwner, data); } else { // we have a NON-endpoint return MoveNonEndpoint(fromHeap, toHeap, newOwner, data); } } /// /// Used internally by the kernel to transfer data that is NOT and endpoint /// to a new owner /// private static Allocation* MoveNonEndpoint(SharedHeap fromHeap, SharedHeap toHeap, Process newOwner, Allocation* data) { // We can only transfer either into our out of the kernel's heap DebugStub.Assert(fromHeap == SharedHeap.KernelSharedHeap || toHeap == SharedHeap.KernelSharedHeap); if (fromHeap != toHeap) { data = toHeap.Move(data, fromHeap, fromHeap.DataOwnerId, toHeap.DataOwnerId); } // update id's in kernel structure and who owns the blocks Allocation.SetOwnerProcessId(data, newOwner.ProcessId); return data; } /// /// Used internally by the kernel to transfer and endpoint data to a new owner /// /// Must update channel structures if we change co-location of peers. /// 1) if currently co-located and no longer co-located after move, must /// allocate new target proxies etc. /// /// 2) if we were not colocated and we are after the move, must join the proxies /// into a direct structure. /// /// private static Allocation* MoveEndpoint(SharedHeap fromHeap, SharedHeap toHeap, Process newOwner, Allocation* ep) { EndpointCore *epc = (EndpointCore*)Allocation.GetDataUnchecked(ep); EndpointTrusted* ept = epc->Trusted; if (ep != ept->self) { // passed in ep only pretends to be an endpoint DebugStub.Break(); } // We can only transfer either into our out of the kernel's heap DebugStub.Assert(fromHeap == SharedHeap.KernelSharedHeap || toHeap == SharedHeap.KernelSharedHeap); if (fromHeap != toHeap) { // First Copy Self Allocation* epCopy = toHeap.ShallowCopy(ep, toHeap.EndpointOwnerId); fromHeap.Free(ep, fromHeap.EndpointOwnerId); ept->self = epCopy; // Determine if we are going to be colocated or not: Process peerProcess = Process.GetProcessByID(ept->Peer->ownerProcessId); SharedHeap peerHeap = peerProcess.ProcessSharedHeap; // Now deal with Peer Allocation* peerAllocation = ept->directPeerAllocation; if (peerHeap != toHeap) { // After the move, we are not colocated with peer. // Copy peer proxy as well Allocation* peerCopy = toHeap.ShallowCopy(peerAllocation, toHeap.EndpointPeerOwnerId); fromHeap.Free(peerAllocation, fromHeap.EndpointPeerOwnerId); ept->directPeerAllocation = peerCopy; } else { // assume peerHeap == toHeap // After the move, we are colocated with peer. // 1) Free peer proxy // 2) Point peer structures at actual peers rather than proxy fromHeap.Free(peerAllocation, fromHeap.EndpointPeerOwnerId); // Now comes a tricky unsafe part. There's a race in that we are // trying to share the peer structure, but that might be concurrently // decrementing its ref count. // TODO: look at shared heap implementation to guard against this. Allocation *truePeer = ept->Peer->self; // CAREFUL: We may not own this guy ept->directPeerAllocation = toHeap.Share(truePeer, toHeap.EndpointPeerOwnerId, UIntPtr.Zero, Allocation.GetSize(truePeer)); // Now we must also update the peer's directPeer to point to us // rather than the proxy! peerHeap.Free(ept->Peer->directPeerAllocation, peerHeap.EndpointPeerOwnerId); ept->Peer->directPeerAllocation = peerHeap.Share(epCopy, peerHeap.EndpointPeerOwnerId, UIntPtr.Zero, Allocation.GetSize(epCopy)); // Make peer process own this shared allocation Allocation.SetOwnerProcessId(ept->Peer->directPeerAllocation, peerProcess.ProcessId); } } // update id's in kernel structure and who owns the blocks TransferToProcess(ept, newOwner); return ept->self; } #endif //PAGING public int ChannelId { [NoHeapAllocation] get { return this.channelId; } } public int ProcessId { [NoHeapAllocation] get { return this.ownerProcessId; } #if PAGING set { this.ownerProcessId = value; } #endif } public int ReceiveCount { [NoHeapAllocation] get { return this.receiveCount; } } /// /// Used from ChannelDiagnostics module to access data it does not own /// public int PeerProcessId { [NoHeapAllocation] get { #if !PAGING EndpointCore* peer = (EndpointCore*)Allocation.GetDataUnchecked(this.peer); return peer->ownerProcessId; #else return Peer->ownerProcessId; #endif } } public PrincipalHandle PrincipalHandle { [NoHeapAllocation] get { return ownerPrincipalHandle; } #if PAGING set { this.ownerPrincipalHandle = value; } #endif } public PrincipalHandle OwnerPrincipalHandle { [NoHeapAllocation] get { return ownerPrincipalHandle; } } public PrincipalHandle PeerPrincipalHandle { [NoHeapAllocation] get { #if !PAGING EndpointCore* peer = (EndpointCore*)Allocation.GetDataUnchecked(this.peer); return peer->ownerPrincipalHandle; #else return peer->ownerPrincipalHandle; #endif } } /// /// Used from ChannelDiagnostics module to access data it does not own /// public int PeerReceiveCount { [NoHeapAllocation] get { #if !PAGING EndpointCore* peer = (EndpointCore*)Allocation.GetDataUnchecked(this.peer); return peer->receiveCount; #else return Peer->receiveCount; #endif } } #if PAGING public void LinkIntoCollection(AutoResetEventHandle ev) { this.collectionEvent = ev; } public void UnlinkFromCollection(AutoResetEventHandle ev) { this.collectionEvent = new AutoResetEventHandle(); } internal void BeginUpdate(byte* basep, byte* source, int* tagAddress, int size) { endpointUpdates.BeginUpdate(basep, source, tagAddress, size); } unsafe internal void MarshallPointer(byte* basep, void** target, SystemType type) { endpointUpdates.MarshallPointer(basep, target, type, Process.GetProcessByID(peer->ownerProcessId)); } #endif } #if PAGING [CLSCompliant(false)] unsafe public struct EndpointUpdate { // Each EndpointUpdate points to a message and tag in the // data area of the EndpointUpdates. The tag is always // a 32-bit word. internal ushort msgOffset; internal ushort msgSize; // message size in bytes internal ushort tagOffset; internal ushort ptrToMarshallOffset0; internal ushort ptrToMarshallOffset1; internal ushort ptrToMarshallOffset2; public void Init(ushort msgOffset, ushort msgSize, ushort tagOffset) { this.msgOffset = msgOffset; this.msgSize = msgSize; this.tagOffset = tagOffset; this.ptrToMarshallOffset0 = 0; this.ptrToMarshallOffset1 = 0; this.ptrToMarshallOffset2 = 0; } public void AddMarshallOffset(ushort ptrOffset) { DebugStub.Assert(ptrOffset != 0); // There's only space for three pointers in out buffer if (ptrToMarshallOffset0 == 0) { ptrToMarshallOffset0 = ptrOffset; return; } if (ptrToMarshallOffset1 == 0) { ptrToMarshallOffset1 = ptrOffset; return; } if (ptrToMarshallOffset2 == 0) { ptrToMarshallOffset2 = ptrOffset; return; } // out of marshall pointer locations DebugStub.Assert(false); } public void CopyData(byte* source, byte* dest) { Buffer.MoveMemory(dest + msgOffset, source + msgOffset, (int)(msgSize)); *((int*)(dest + tagOffset)) = *((int*)(source + tagOffset)); } private static void MarshallPointer(byte* source, byte* dest, ref ushort offsetRef) { ushort offset = offsetRef; if (offset == 0) return; // unused Allocation** srcptr = (Allocation**)(source + offset); // update the pointer in the kernel space *srcptr = EndpointTrusted.MoveData(SharedHeap.KernelSharedHeap, Thread.CurrentProcess.ProcessSharedHeap, Thread.CurrentProcess, *srcptr); offsetRef = 0; // mark unused } public void MarshallAndCopyData(byte* source, byte* dest) { // Marshall in kernel space MarshallPointer(source, dest, ref ptrToMarshallOffset0); MarshallPointer(source, dest, ref ptrToMarshallOffset1); MarshallPointer(source, dest, ref ptrToMarshallOffset2); CopyData(source, dest); } } [CLSCompliant(false)] unsafe public struct EndpointUpdates { // The buffer holds two areas: // - Endpoint data (same layout as Endpoint) // - EndpointUpdate values (one for each word in the data) private Allocation* buffer; private int pending; // number of currently pending updates private EndpointUpdate* updates; // interior pointer into buffer.data private EndpointUpdate current; // currently being constructed internal void BeginUpdate(byte* basep, byte* source, int* tagAddress, int size) { DebugStub.Assert(source >= basep); DebugStub.Assert(source - basep < 0x10000); DebugStub.Assert(0 <= size && size < 0x10000); DebugStub.Assert((byte*)tagAddress >= basep); DebugStub.Assert((byte*)tagAddress - basep < 0x10000); ushort msgOffset = (ushort)(source-basep); ushort msgSize = (ushort)size; ushort tagOffset = (ushort)((byte*)tagAddress-basep); this.current.Init(msgOffset, msgSize, tagOffset); this.current.CopyData(basep, GetBufferData()); } internal void EndUpdate() { this.Add(this.current); } unsafe internal void MarshallPointer(byte* basep, void** target, SystemType type, Process newOwner) { long offset = (byte*)target-basep; DebugStub.Assert(offset < 0x10000); byte* kernelBuffer = GetBufferData(); Allocation** dstPtr = ((Allocation**)(kernelBuffer+offset)); Allocation* userPtr = *dstPtr; SharedHeap.CurrentProcessSharedHeap.Validate(userPtr); // Validate that pointer passed is of the required type if (!SystemType.IsSubtype(userPtr, type)) { // Trusted marshaller thinks we are supposed to pass something other // than the user passed. DebugStub.Break(); } // Copy it into the kernel space and update the pointer // in the kernel space buffer *dstPtr = EndpointTrusted.MoveData( Thread.CurrentProcess.ProcessSharedHeap, SharedHeap.KernelSharedHeap, newOwner, userPtr); this.current.AddMarshallOffset( (ushort)offset ); } internal void Initialize(Allocation* endpoint) { UIntPtr size = Allocation.GetSize(endpoint); DebugStub.Assert(size < 0x10000); // must fit in ushort // TODO: it would be better not to allocate an allocation record here: buffer = SharedHeap.KernelSharedHeap.Allocate( size + 10*sizeof(EndpointUpdate), // allocate endpoint plus 10 update records typeof(EndpointUpdate).GetSystemType().TypeId, 0, SharedHeap.KernelSharedHeap.DataOwnerId); Allocation.SetOwnerProcessId(buffer, Process.kernelProcess.ProcessId); pending = 0; updates = (EndpointUpdate*) (GetBufferData() + (int)size); } internal void FreeResources() { SharedHeap.KernelSharedHeap.Free(buffer, SharedHeap.KernelSharedHeap.DataOwnerId); } public Allocation* GetBuffer() { return buffer; } public byte* GetBufferData() { return (byte*) Allocation.GetDataUnchecked(buffer); } // Add an update to the buffer. // The data pointed to by the update should already be in "buffer". // Called in the sender's thread. public void Add(EndpointUpdate update) { // MULTIPROCESSOR NOTE: tricky concurrency between sender and receiver int n = pending; DebugStub.Assert(updates + (n + 1) <= GetBufferData() + (int)(Allocation.GetSize(buffer))); while (true) { *(updates + n) = update; int old = Interlocked.CompareExchange(ref pending, n + 1, n); if (old == n) { break; } // Receiver just flushed the updates and didn't see ours, so // put the update at index 0. DebugStub.Assert(old == 0); n = 0; } } /* public void Add(byte* msgPtr, int msgSize, byte* tagPtr) { byte* data = GetBufferData(); DebugStub.Assert(msgPtr >= data); DebugStub.Assert(msgPtr - data < 0x10000); DebugStub.Assert(0 <= msgSize && msgSize < 0x10000); DebugStub.Assert(tagPtr >= data); DebugStub.Assert(tagPtr - data < 0x10000); Add(new EndpointUpdate( (ushort)(msgPtr - data), (ushort)msgSize, (ushort)(tagPtr - data DebugStub.Assert(msgPtr >= data); DebugStub.Assert(msgPtr - data < 0x10000); DebugStub.Assert(0 <= msgSize && msgSize < 0x10000); DebugStub.Assert(tagPtr >= data); DebugStub.Assert(tagPtr - data < 0x10000); ))); } */ // Flush all pending updates into the destination Endpoint. // Called in the receiver's thread. public void Fetch(Allocation* destEp) { // MULTIPROCESSOR NOTE: tricky concurrency between sender and receiver int n = pending; int i = 0; while (i < n) { for (; i < n; i++) { (updates + i)->MarshallAndCopyData(GetBufferData(), (byte*) (Allocation.GetData(destEp))); } // We've delivered all the updates that we know of (n==i), // so try to change pending from n to 0. If this succeeds, // n will stay the same (n==i). Otherwise, n will be a more // recent value of pending (n>i). n = Interlocked.CompareExchange(ref pending, 0, n); } } } // The portion of the endpoint that is untrusted and may // contain random garbage. [CLSCompliant(false)] [CCtorIsRunDuringStartup] unsafe public struct EndpointCore { private EndpointTrusted* id; [NoHeapAllocation] internal EndpointCore(EndpointTrusted* trusted) { this.id = trusted; } internal EndpointTrusted* Trusted { [NoHeapAllocation] get { return id; } } private static EndpointTrusted* AllocationTrusted( Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint) { return ((EndpointCore*)Allocation.GetData(endpoint))->Trusted; } public static int OpenChannelCount { get { return EndpointTrusted.OpenChannelCount; } } /// /// Closes this end of the channel and frees associated resources, EXCEPT the block /// of memory for this endpoint. It must be released by the caller. Sing# does this /// for the programmer. /// /// This runs in the kernel to avoid a race condition with Process.Stop. public static void Dispose(ref EndpointCore endpoint) { EndpointTrusted.Dispose(endpoint.Trusted); } /// /// Explicitly frees this end of the channel. If other end is also /// freed, the channel is deallocated, meaning we have to deallocate /// the kernel handles for the auto reset events. /// Since both threads on the channel could try to do this simultaneously, /// we use the ref counting on the underlying endpoints to let the last /// free operation (the one pulling the ref count to 0) to free the associated /// event. /// public static void Free(Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint) { EndpointTrusted.Free(AllocationTrusted(endpoint)); } /// /// Performs the initialization of the core part of each endpoint and cross links /// them to form a channel. /// public static void Connect( Allocation* /*EndpointCore* opt(ExHeap)!*/ imp, Allocation* /*EndpointCore* opt(ExHeap)!*/ exp) { // TODO: allocate handle in local process handle table //Process process = Thread.CurrentProcess; Process process = Process.kernelProcess; EndpointTrusted *impEp = EndpointTrusted.New(imp); EndpointTrusted *expEp = EndpointTrusted.New(exp); EndpointCore *impCore = (EndpointCore*)Allocation.GetData(imp); EndpointCore *expCore = (EndpointCore*)Allocation.GetData(exp); impCore->id = impEp; expCore->id = expEp; EndpointTrusted.Connect(impEp, expEp, imp, exp); } /// /// Set this end to closed /// public void Close() { Trusted->Close(); } public bool Closed() { return Trusted->Closed(); } public bool PeerClosed() { // make sure to flush updates from peer into this endpoint Trusted->Peer->FlushTo(Trusted->Self); return Trusted->Peer->Closed(); } /// /// Return a handle for the peer /// public Allocation* Peer(out bool marshall) { Allocation* peerAlloc = Trusted->DirectPeerAllocation(out marshall); return peerAlloc; } /// /// The event to wait for messages on this endpoint. Used by Select. /// public SyncHandle GetWaitHandle() { return Trusted->GetWaitHandle(); } /// /// Notify the peer of this endpoint that a message is ready. /// Notifies the set owner if this endpoint is part of a set. /// public void NotifyPeer() { Trusted->NotifyPeer(); } /// /// Used internally by the kernel to transfer an endpoint to a new owner /// /// Can be used to transfer ANY kind of shared heap data, not just endpoints. /// /// The argument ep must be an endpoint. public static Allocation* MoveEndpoint(SharedHeap fromHeap, SharedHeap toHeap, Process newOwner, Allocation *ep) { return EndpointTrusted.MoveData(fromHeap, toHeap, newOwner, ep); } /// /// Transfer the given Allocation block to the target endpoint /// // TODO: change "ref EndpointCore" to "EndpointCore" // TODO: rethink this operation public static void TransferBlockOwnership(Allocation* ptr, ref EndpointCore target) { Monitoring.Log(Monitoring.Provider.ChannelService, (ushort)ChannelServiceEvent.TransferBlockOwnership, 0, (uint)target.Trusted->ChannelId, (uint)target.Trusted->ProcessId, 0, 0, 0); Allocation.SetOwnerProcessId(ptr, target.Trusted->ProcessId); } /// /// Transfer any contents that needs to be adjusted from the transferee to the target /// endpoint. Currently, this means setting the ownerProcessId of the /// transferee to that of the target. /// // TODO: change "ref EndpointCore" to "EndpointCore" public static void TransferContentOwnership( ref EndpointCore transferee, ref EndpointCore target) { Monitoring.Log(Monitoring.Provider.ChannelService, (ushort)ChannelServiceEvent.TransferContentOwnership, 0, (uint)transferee.Trusted->ProcessId, (uint)target.Trusted->ProcessId, (uint)transferee.Trusted->ChannelId, (uint)target.Trusted->ChannelId, (uint)target.Trusted->Peer->ChannelId); EndpointTrusted* ept = transferee.Trusted; int toProcessId = target.Trusted->ProcessId; PrincipalHandle toPrincipalHandle = target.Trusted->PrincipalHandle; ept->ProcessId = toProcessId; ept->PrincipalHandle = toPrincipalHandle; // also transfer the peer allocation Allocation.SetOwnerProcessId(ept->directPeerAllocation, toProcessId); } public int ChannelId { [NoHeapAllocation] get { return Trusted->ChannelId; } } public int ProcessId { [NoHeapAllocation] get { return Trusted->ProcessId; } } public int ReceiveCount { get { return Trusted->ReceiveCount; } } public int PeerProcessId { [NoHeapAllocation] get { return Trusted->PeerProcessId; } } public PrincipalHandle OwnerPrincipalHandle { [NoHeapAllocation] get { return Trusted->OwnerPrincipalHandle; } } public PrincipalHandle PeerPrincipalHandle { [NoHeapAllocation] get { return Trusted->PeerPrincipalHandle; } } public int PeerReceiveCount { [NoHeapAllocation] get { return Trusted->PeerReceiveCount; } } public void LinkIntoCollection(AutoResetEventHandle ev) { Trusted->LinkIntoCollection(ev); } public void UnlinkFromCollection(AutoResetEventHandle ev) { Trusted->UnlinkFromCollection(ev); } internal void BeginUpdate(byte* basep, byte* source, int* tagAddress, int size) { Trusted->BeginUpdate(basep, source, tagAddress, size); } unsafe public void MarshallPointer(byte* basep, void** target, SystemType type) { if (target == null) return; Trusted->MarshallPointer(basep, target, type); } } #endif }