EAGlobal.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. ///////////////////////////////////////////////////////////////////////////////
  5. // OS globals are process-wide globals and are shared between an EXE and
  6. // DLLs. The OS global system works at the operating system level and has
  7. // auto-discovery logic so that no pointers or init calls need to be made
  8. // between modules for them to link their OS global systems together.
  9. //
  10. // Note that the interface to OS globals is a bit convoluted because the
  11. // core system needs to be thread-safe, cross-module, and independent of
  12. // app-level allocators. For objects for which order of initialization is
  13. // clearer, EASingleton is probably a better choice.
  14. ///////////////////////////////////////////////////////////////////////////////
  15. #include <EAStdC/EAGlobal.h>
  16. #include <EAStdC/internal/Thread.h>
  17. #include <EAStdC/EASprintf.h>
  18. #include <stdlib.h>
  19. #include <new>
  20. #include <EAAssert/eaassert.h>
  21. // Until we can test other implementations of Linux, we enable Linux only for regular desktop x86 Linux.
  22. #if (defined(EA_PLATFORM_LINUX) && (defined(EA_PROCESSOR_X86) || defined(EA_PROCESSOR_X86_64)) && !defined(EA_PLATFORM_ANDROID)) // What other unix-like platforms can do this?
  23. #include <semaphore.h>
  24. #include <fcntl.h>
  25. #include <sys/mman.h>
  26. #include <stdlib.h>
  27. #include <unistd.h>
  28. #define EASTDC_EAGLOBAL_UNIX 1
  29. #else
  30. #define EASTDC_EAGLOBAL_UNIX 0
  31. #endif
  32. #if defined(EA_PLATFORM_MICROSOFT)
  33. #pragma warning(push, 0)
  34. #include <Windows.h>
  35. #pragma warning(pop)
  36. #endif
  37. #if defined(EA_PLATFORM_MICROSOFT)
  38. #pragma warning(push)
  39. #pragma warning(disable: 4355) // warning C4355: 'this' : used in base member initializer list
  40. #ifndef EASTDC_INIT_SEG_DEFINED
  41. #define EASTDC_INIT_SEG_DEFINED
  42. // Set initialization order between init_seg(compiler) (.CRT$XCC) and
  43. // init_seg(lib) (.CRT$XCL). The MSVC linker sorts the .CRT sections
  44. // alphabetically so we simply need to pick a name that is between
  45. // XCC and XCL. This works on both Windows and XBox.
  46. #pragma warning(disable: 4075) // "initializers put in unrecognized initialization area"
  47. #pragma init_seg(".CRT$XCF")
  48. #endif
  49. #endif
  50. #if EASTDC_GLOBALPTR_SUPPORT_ENABLED
  51. namespace
  52. {
  53. struct OSGlobalManager
  54. {
  55. typedef EA::StdC::intrusive_list<EA::StdC::OSGlobalNode> OSGlobalList;
  56. OSGlobalList mOSGlobalList;
  57. uint32_t mRefCount; //< Atomic reference count so that the allocator persists as long as the last module that needs it.
  58. EA::StdC::Mutex mcsLock;
  59. OSGlobalManager();
  60. OSGlobalManager(const OSGlobalManager&);
  61. OSGlobalManager& operator=(const OSGlobalManager&);
  62. void Lock() {
  63. mcsLock.Lock();
  64. }
  65. void Unlock() {
  66. mcsLock.Unlock();
  67. }
  68. EA::StdC::OSGlobalNode* Find(uint32_t id);
  69. void Add(EA::StdC::OSGlobalNode* p);
  70. void Remove(EA::StdC::OSGlobalNode* p);
  71. };
  72. OSGlobalManager::OSGlobalManager() {
  73. EA::StdC::AtomicSet(&mRefCount, 0);
  74. }
  75. EA::StdC::OSGlobalNode* OSGlobalManager::Find(uint32_t id) {
  76. OSGlobalList::iterator it(mOSGlobalList.begin()), itEnd(mOSGlobalList.end());
  77. for(; it!=itEnd; ++it) {
  78. EA::StdC::OSGlobalNode& node = *it;
  79. if (node.mOSGlobalID == id)
  80. return &node;
  81. }
  82. return NULL;
  83. }
  84. void OSGlobalManager::Add(EA::StdC::OSGlobalNode *p) {
  85. mOSGlobalList.push_front(*p);
  86. }
  87. void OSGlobalManager::Remove(EA::StdC::OSGlobalNode *p) {
  88. OSGlobalList::iterator it = mOSGlobalList.locate(*p);
  89. mOSGlobalList.erase(it);
  90. }
  91. OSGlobalManager* gpOSGlobalManager = NULL;
  92. uint32_t gOSGlobalRefs = 0;
  93. } // namespace
  94. #if defined(EA_PLATFORM_MICROSOFT)
  95. namespace {
  96. #define EA_GLOBAL_UNIQUE_NAME_FORMAT "SingleMgrMutex%08x"
  97. #define EA_GLOBAL_UNIQUE_NAME_FORMAT_W L"SingleMgrMutex%08x"
  98. // For the Microsoft desktop API (e.g. Win32, Win64) we can use Get/SetEnvironmentVariable to
  99. // read/write a process-global variable. But other Microsoft APIs (e.g. XBox 360) don't support
  100. // this and we resort to using a semaphore to store pointer bits.
  101. #if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 4)
  102. HANDLE ghOSGlobalManagerPtrSemaphore;
  103. #elif !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 8)
  104. HANDLE ghOSGlobalManagerPtrSemaphoreHi = NULL;
  105. HANDLE ghOSGlobalManagerPtrSemaphoreLo = NULL;
  106. #endif
  107. bool InitOSGlobalSystem();
  108. void ShutdownOSGlobalSystem();
  109. OSGlobalManager* CreateOSGlobalManager();
  110. void DestroyOSGlobalManager(OSGlobalManager* pOSGlobalManager);
  111. OSGlobalManager* CreateOSGlobalManager()
  112. {
  113. // Allocate the OSGlobal manager in the heap. We use the heap so that it can
  114. // hop between DLLs if the EXE itself doesn't use the manager. Note that this
  115. // must be the operating system heap and not an app-level heap (i.e. PPMalloc).
  116. // We store the pointer to the originally allocated memory at p[-1], because we
  117. // may have moved it during alignment.
  118. const size_t kAlignment = 16;
  119. void* p = HeapAlloc(GetProcessHeap(), 0, sizeof(OSGlobalManager) + kAlignment - 1 + sizeof(void *));
  120. void* pAligned = (void *)(((uintptr_t)p + sizeof(void *) + kAlignment - 1) & ~(kAlignment-1));
  121. ((void**)pAligned)[-1] = p;
  122. // Placement-new the global manager into the new memory.
  123. return new(pAligned) OSGlobalManager;
  124. }
  125. void DestroyOSGlobalManager(OSGlobalManager* pOSGlobalManager)
  126. {
  127. if(pOSGlobalManager)
  128. {
  129. gpOSGlobalManager->~OSGlobalManager();
  130. HeapFree(GetProcessHeap(), 0, ((void**)gpOSGlobalManager)[-1]);
  131. }
  132. }
  133. bool InitOSGlobalSystem()
  134. {
  135. // The following check is not thread-safe. On most platforms this isn't an
  136. // issue in practice because this function is called on application startup
  137. // and other threads won't be active. The primary concern is if the
  138. // memory changes that result below are visible to other processors later.
  139. if (!gpOSGlobalManager)
  140. {
  141. // We create a named (process-global) mutex. Other threads or modules within
  142. // this process share this same underlying mutex.
  143. #if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
  144. // The kernel object namespace is global on Win32 so we have to choose a unique name.
  145. wchar_t uniqueName[64];
  146. EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT_W, (unsigned)GetCurrentProcessId());
  147. HANDLE hMutex = CreateMutexExW(NULL, uniqueName, CREATE_MUTEX_INITIAL_OWNER, SYNCHRONIZE);
  148. #else
  149. // The kernel object namespace is global on Win32 so we have to choose a unique name.
  150. char uniqueName[64];
  151. EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned)GetCurrentProcessId());
  152. HANDLE hMutex = CreateMutexA(NULL, FALSE, uniqueName);
  153. #endif
  154. if (!hMutex)
  155. return false;
  156. if (WAIT_FAILED != WaitForSingleObjectEx(hMutex, INFINITE, FALSE))
  157. {
  158. // If we don't have access to GetEnvironmentVariable and are a 32 bit platform...
  159. #if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 4)
  160. // Xenon does not have memory mapping so we can't use the same technique
  161. // as for Win32, and lacks GetModuleHandle() so the old "get global proc
  162. // from main executable" trick won't work. What we do here is create
  163. // a semaphore whose value is the pointer to gpOSGlobalManager. Semaphores
  164. // can't hold negative values so we shift the pointer down by 4 bits
  165. // before storing it (it has 16 byte alignment so this is OK). Also,
  166. // there is no way to read a semaphore without modifying it so a mutex
  167. // is needed around the operation.
  168. wchar_t uniqueSemaphoreName[32];
  169. EA::StdC::Sprintf(uniqueSemaphoreName, L"SingleMgr%u", (unsigned)GetCurrentProcessId());
  170. ghOSGlobalManagerPtrSemaphore = CreateSemaphoreExW(NULL, 0, 0x7FFFFFFF, uniqueSemaphoreName, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
  171. if (ghOSGlobalManagerPtrSemaphore)
  172. {
  173. const bool bSemaphoreExists = (GetLastError() == ERROR_ALREADY_EXISTS);
  174. if (bSemaphoreExists) // If somebody within our process already created it..
  175. {
  176. LONG ptrValue;
  177. // Read the semaphore value.
  178. if (ReleaseSemaphore(ghOSGlobalManagerPtrSemaphore, 1, &ptrValue)) {
  179. // Undo the +1 we added to the semaphore.
  180. WaitForSingleObjectEx(ghOSGlobalManagerPtrSemaphore, INFINITE, FALSE);
  181. // Recover the allocator pointer from the semaphore's original value.
  182. gpOSGlobalManager = (OSGlobalManager *)((uintptr_t)ptrValue << 4);
  183. }
  184. else
  185. EA_FAIL();
  186. }
  187. else
  188. {
  189. gpOSGlobalManager = CreateOSGlobalManager();
  190. // Set the semaphore to the pointer value. It was created with
  191. // zero as the initial value so we add the desired value here.
  192. ReleaseSemaphore(ghOSGlobalManagerPtrSemaphore, (LONG)((uintptr_t)gpOSGlobalManager >> 4), NULL);
  193. }
  194. }
  195. else
  196. {
  197. // We have a system failure we have no way of handling.
  198. EA_FAIL();
  199. }
  200. // If we don't have access to GetEnvironmentVariable and are a 64 bit platform...
  201. #elif !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 8)
  202. // Semaphore counts are limited to 31 bits (LONG_MAX), but we need to store a 64 bit pointer
  203. // in those bits. But 64 bit pointers are always 64 bit aligned, so we need only 61 bits
  204. // to store a 64 bit pointer. So we store the upper 31 bits in one semaphore and the lower
  205. // 30 bits in another semaphore. Take the resulting 61 bits and shift left by 3 to get the
  206. // full 64 bit pointer.
  207. // We use CreateSemaphoreExW instead of CreateSemaphoreW because the latter isn't always
  208. // present in the non-desktop API.
  209. // The kernel object namespace is session-local (not the same as app-local) so we have to choose a unique name.
  210. wchar_t uniqueNameHi[64];
  211. wchar_t uniqueNameLo[64];
  212. DWORD dwProcessId = GetCurrentProcessId();
  213. EA::StdC::Sprintf(uniqueNameHi, L"SingleMgrHi%u", dwProcessId);
  214. EA::StdC::Sprintf(uniqueNameLo, L"SingleMgrLo%u", dwProcessId);
  215. // Create the high semaphore with a max of 31 bits of storage (0x7FFFFFFF).
  216. ghOSGlobalManagerPtrSemaphoreHi = CreateSemaphoreExW(NULL, 0, 0x7FFFFFFF, uniqueNameHi, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
  217. if(ghOSGlobalManagerPtrSemaphoreHi)
  218. {
  219. const bool bSemaphoreExists = (GetLastError() == ERROR_ALREADY_EXISTS);
  220. LONG ptrValueHi;
  221. LONG ptrValueLo;
  222. if(bSemaphoreExists) // If somebody within our process already created it..
  223. {
  224. // Read the semaphore value.
  225. if(ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreHi, 1, &ptrValueHi))
  226. {
  227. WaitForSingleObjectEx(ghOSGlobalManagerPtrSemaphoreHi, INFINITE, FALSE); // Undo the +1 we added with ReleaseSemaphore.
  228. // We still need to create our ghOSGlobalManagerPtrSemaphoreLo, which should also already exist,
  229. // since some other module in this process has already exected this function. Create it with a
  230. // max of 30 bits of storage (0x3FFFFFFF).
  231. EA_ASSERT(ghOSGlobalManagerPtrSemaphoreLo == NULL);
  232. ghOSGlobalManagerPtrSemaphoreLo = CreateSemaphoreExW(NULL, 0, 0x3FFFFFFF, uniqueNameLo, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
  233. EA_ASSERT(GetLastError() == ERROR_ALREADY_EXISTS);
  234. ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreLo, 1, &ptrValueLo);
  235. WaitForSingleObjectEx(ghOSGlobalManagerPtrSemaphoreLo, INFINITE, FALSE); // Undo the +1 we added with ReleaseSemaphore.
  236. // Recover the allocator pointer from the semaphore's original value.
  237. uintptr_t ptr = (((uintptr_t)ptrValueHi) << 30 | ptrValueLo) << 3; // Combine the pair into 61 bits, and shift left by 3. This is the reverse of the code below.
  238. gpOSGlobalManager = (OSGlobalManager*)ptr;
  239. }
  240. else
  241. EA_FAIL(); // In practice this cannot happen unless the machine is cripped beyond repair.
  242. }
  243. else // Else our CreateSemaphorExW call was the first one to create ghOSGlobalManagerPtrSemaphoreHi.
  244. {
  245. gpOSGlobalManager = CreateOSGlobalManager();
  246. EA_ASSERT(gpOSGlobalManager && (((uintptr_t)gpOSGlobalManager & 7) == 0)); // All pointers on 64 bit platforms should have their lower 3 bits unused.
  247. // Set the semaphore to the pointer value. It was created with
  248. // zero as the initial value so we add the desired value here.
  249. uintptr_t ptr = (uintptr_t)gpOSGlobalManager >> 3; // ptr now has the 61 significant bits of gpOSGlobalManager.
  250. ptrValueHi = static_cast<LONG>(ptr >> 30); // ptrValueHi has the upper 31 of the 61 bits.
  251. ptrValueLo = static_cast<LONG>(ptr & 0x3FFFFFFF); // ptrValueLo has the lower 30 of the 61 bits.
  252. // We still need to create our ghOSGlobalManagerPtrSemaphoreLo, which should also not already exist.
  253. // Create it with a max of 30 bits of storage (0x3FFFFFFF).
  254. EA_ASSERT(ghOSGlobalManagerPtrSemaphoreLo == NULL);
  255. ghOSGlobalManagerPtrSemaphoreLo = CreateSemaphoreExW(NULL, 0, 0x3FFFFFFF, uniqueNameLo, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
  256. EA_ASSERT(GetLastError() != ERROR_ALREADY_EXISTS);
  257. EA_ASSERT((ghOSGlobalManagerPtrSemaphoreHi != NULL) && (ghOSGlobalManagerPtrSemaphoreLo != NULL)); // Should always be true, due to the logic in this function.
  258. ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreHi, ptrValueHi, NULL);
  259. ReleaseSemaphore(ghOSGlobalManagerPtrSemaphoreLo, ptrValueLo, NULL);
  260. // Now semaphoreHi has the upper 31 significant bits of the gpOSGlobalManager pointer value.
  261. // and semaphoreLo has the lower 30 significant bits of the gpOSGlobalManager pointer value.
  262. }
  263. }
  264. else
  265. {
  266. // We have a system failure we have no way of handling.
  267. EA_FAIL();
  268. }
  269. #else
  270. // Under Win32 and Win64, we use system environment variables to store the gpOSGlobalManager value.
  271. char stringPtr[32];
  272. const DWORD dwResult = GetEnvironmentVariableA(uniqueName, stringPtr, 32);
  273. if((dwResult > 0) && dwResult < 32 && stringPtr[0]) // If the variable was found...
  274. gpOSGlobalManager = (OSGlobalManager *)(uintptr_t)_strtoui64(stringPtr, NULL, 16); // _strtoui64 is a VC++ extension function.
  275. else
  276. {
  277. // GetLastError() should be ERROR_ENVVAR_NOT_FOUND. But what do we do if it isn't?
  278. gpOSGlobalManager = CreateOSGlobalManager();
  279. EA::StdC::Sprintf(stringPtr, "%I64x", (uint64_t)(uintptr_t)gpOSGlobalManager);
  280. SetEnvironmentVariableA(uniqueName, stringPtr); // There's not much we can do if this call fails.
  281. }
  282. #endif
  283. EA_ASSERT(gpOSGlobalManager && (gpOSGlobalManager->mRefCount < UINT32_MAX));
  284. EA::StdC::AtomicIncrement(&gpOSGlobalManager->mRefCount);
  285. BOOL result = ReleaseMutex(hMutex);
  286. EA_ASSERT(result); EA_UNUSED(result);
  287. }
  288. BOOL result = CloseHandle(hMutex);
  289. EA_ASSERT(result); EA_UNUSED(result);
  290. if (!gpOSGlobalManager)
  291. {
  292. ShutdownOSGlobalSystem();
  293. return false;
  294. }
  295. EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
  296. EA::StdC::AtomicIncrement(&gOSGlobalRefs); // Increment it once for the init of this system (InitOSGlobalSystem). This increment will be matched by a decrement in ShutdownOSGlobalSystem.
  297. }
  298. return true;
  299. }
  300. void ShutdownOSGlobalSystem()
  301. {
  302. if (EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
  303. {
  304. if (gpOSGlobalManager)
  305. {
  306. if (EA::StdC::AtomicDecrement(&gpOSGlobalManager->mRefCount) == 0)
  307. DestroyOSGlobalManager(gpOSGlobalManager);
  308. gpOSGlobalManager = NULL;
  309. }
  310. #if !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 4)
  311. if (ghOSGlobalManagerPtrSemaphore)
  312. {
  313. CloseHandle(ghOSGlobalManagerPtrSemaphore);
  314. ghOSGlobalManagerPtrSemaphore = NULL;
  315. }
  316. #elif !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP) && (EA_PLATFORM_PTR_SIZE == 8)
  317. if (ghOSGlobalManagerPtrSemaphoreHi)
  318. {
  319. CloseHandle(ghOSGlobalManagerPtrSemaphoreHi);
  320. ghOSGlobalManagerPtrSemaphoreHi = NULL;
  321. }
  322. if (ghOSGlobalManagerPtrSemaphoreLo)
  323. {
  324. CloseHandle(ghOSGlobalManagerPtrSemaphoreLo);
  325. ghOSGlobalManagerPtrSemaphoreLo = NULL;
  326. }
  327. #else
  328. // Clear the gpOSGlobalManager environment variable.
  329. // This code needs to be called in a thread-safe way by the user, usually by calling it once on shutdown.
  330. // We have a problem if this function is executing at the same time some other entity in this process
  331. // is currently doing some new use of the OS global system, as that can cause an instance of gpOSGlobalManager
  332. // to be created while we are in the process of destroying it.
  333. char uniqueName[64]; uniqueName[0] = 0;
  334. EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned)GetCurrentProcessId());
  335. SetEnvironmentVariableA(uniqueName, NULL);
  336. #endif
  337. }
  338. }
  339. } // namespace
  340. #elif defined(EA_PLATFORM_SONY)
  341. // On this platform we use sceKernelReserveVirtualRange with a specific predetermined address, and place our
  342. // OSGlobalManager there. There's a bit of careful code below to deal with possible snafus while doing this,
  343. // and even with that this code still has some caveats about its usage, as described below. Most of the time
  344. // those caveats won't come into play, as they are relevant only for very unusual application usage patterns
  345. // and can still be worked around if those patterns happen to come into play.
  346. #include <sys/dmem.h>
  347. #include <kernel.h>
  348. #include <string.h>
  349. #include <sceerror.h>
  350. #include <eathread/eathread_sync.h>
  351. #include <EAStdC/EAStopwatch.h>
  352. struct OSGlobalManagerContainer
  353. {
  354. uint8_t mMagicNumber[16]; // This is a unique value which guarantees that this is the address of the OSGlobalManager.
  355. OSGlobalManager mOSGlobalManager;
  356. };
  357. const size_t kMemSize = 16384;
  358. const uint64_t kAddr64 = SCE_KERNEL_APP_MAP_AREA_END_ADDR - kMemSize; // End of appication memory space. https://ps4.scedev.net/resources/documents/SDK/3.000/Kernel-Overview/0004.html
  359. const uint8_t kMagic[16] = { 0xD1, 0x4B, 0x78, 0x49, 0x81, 0xF1, 0x45, 0x0D, 0x91, 0x08, 0x1B, 0xA8, 0xE7, 0x8A, 0xD1, 0xD3 }; // Random bytes.
  360. const size_t kDirectMemoryLength = 16 * 1024; // 16 KB minimum for sceKernelAllocateDirectMemory
  361. bool gbKeepDirectMemory = false;
  362. off_t gDirectMemoryStartAddr = 0;
  363. bool InitOSGlobalSystem()
  364. {
  365. // The code below involves a number of careful steps to implement sharing a memory address between DLLs.
  366. // We have this code because this platform provides no other means for sharing a global piece of memory
  367. // between modules. On other platforms (e.g. Unix) there are environment variables we can use, and on
  368. // others (e.g. Windows) there are uniquely named synchronization primitives we can use. On still others
  369. // there are writable semaphore disk files that can be used. On this platform there is no environment variable
  370. // support, synchronization primitives have names but are not unique like on Windows, and disk files do
  371. // exist but only if you tag your application manifest to support the /download0 file mount.
  372. if(!gpOSGlobalManager)
  373. {
  374. uint64_t currentAddr = kAddr64;
  375. int32_t err = sceKernelAllocateDirectMemory(
  376. 0,
  377. SCE_KERNEL_MAIN_DMEM_SIZE,
  378. kDirectMemoryLength,
  379. 0,
  380. #if defined(EA_PLATFORM_PS4)
  381. SCE_KERNEL_WB_ONION,
  382. #else
  383. SCE_KERNEL_MTYPE_C_SHARED,
  384. #endif
  385. &gDirectMemoryStartAddr);
  386. EA_ASSERT(err == SCE_OK);
  387. if(err != SCE_OK)
  388. {
  389. return false;
  390. }
  391. gbKeepDirectMemory = false;
  392. while (!gpOSGlobalManager && (currentAddr >= (kAddr64 - EA::StdC::kKettleOSGlobalSearchSpace)))
  393. {
  394. void* addr = reinterpret_cast<void*>(currentAddr);
  395. OSGlobalManagerContainer* pOSGlobalManagerContainer = static_cast<OSGlobalManagerContainer*>(addr);
  396. int result = SCE_OK; // Disabled because it doesn't work how we need it to: = sceKernelReserveVirtualRange(&addr, kMemSize, SCE_KERNEL_MAP_FIXED | SCE_KERNEL_MAP_NO_OVERWRITE, 0);
  397. // Possible return values are SCE_OK, SCE_KERNEL_ERROR_EINVAL, and SCE_KERNEL_ERROR_ENOMEM.
  398. if((result == SCE_KERNEL_ERROR_ENOMEM) || (result == SCE_OK))
  399. {
  400. // SCE_KERNEL_ERROR_ENOMEM occurs when the address has already been reserved, which may have been
  401. // done by a previous EAGlobal init occurred. With either that or SCE_OK, we call scekernelMapDirectMemory.
  402. // We call scekernelMapDirectMemory even if we get SCE_KERNEL_ERROR_ENOMEM because another thread may
  403. // have called sceKernelReserveVirtualRange first, but we may be executing simultaneously and execute
  404. // sceKernelMapDirectMemory first.
  405. result = sceKernelMapNamedDirectMemory(&addr, kMemSize, SCE_KERNEL_PROT_CPU_READ | SCE_KERNEL_PROT_CPU_WRITE,
  406. SCE_KERNEL_MAP_FIXED | SCE_KERNEL_MAP_NO_OVERWRITE, gDirectMemoryStartAddr, 0, "EAOSGlobal");
  407. // Possible return values are SCE_OK, SCE_KERNEL_ERROR_EACCES, SCE_KERNEL_ERROR_EINVAL, and SCE_KERNEL_ERROR_ENOMEM.
  408. // Normally we expect that SCE_KERNEL_ERROR_ENOMEM or SCE_OK will be returned. If the sceKernelReserveVirtualRange
  409. // returned SCE_OK then usually sceKernalMapDirectMemory will return SCE_OK for us, because if we were the first
  410. // to call the reserve function then we will probably be the first to call the map function.
  411. if((result == SCE_KERNEL_ERROR_ENOMEM) || (result == SCE_OK))
  412. {
  413. // At this point the memory at addr is mapped to our address space and we can attempt to read and write it.
  414. // To make sure this memory really is read/write for us, we query the kernel for its protection type.
  415. bool weWereHereFirst = (result == SCE_OK); // IF we were here first then we take care of initializing the pOSGlobalManagerContainer->mOSGlobalManager instance.
  416. if(weWereHereFirst)
  417. {
  418. // We use some low level synchronization primitives here to accomplish memory synchronization. We are unable
  419. // to use a mutex to do this because there is no way to share a mutex anonymously between modules. The whole
  420. // reason EAGlobal exists is to allow sharing variables anonymously between modules. So we have a chicken and
  421. // egg problem: we can't share a mutex between modules until InitOSGlobalSystem has completed.
  422. ::new(&pOSGlobalManagerContainer->mOSGlobalManager) OSGlobalManager;
  423. EA::StdC::AtomicIncrement(&pOSGlobalManagerContainer->mOSGlobalManager.mRefCount);
  424. gpOSGlobalManager = &pOSGlobalManagerContainer->mOSGlobalManager;
  425. EAWriteBarrier(); // Make sure this is seen as written before the memcpy is seen as written.
  426. EACompilerMemoryBarrier(); // Don't let the compiler move the above code to after the below code.
  427. memcpy(pOSGlobalManagerContainer->mMagicNumber, kMagic, sizeof(pOSGlobalManagerContainer->mMagicNumber));
  428. EAWriteBarrier(); // Make sure other threads see this write.
  429. gbKeepDirectMemory = true;
  430. }
  431. else // Else somebody before us mapped this memory. We need to validate it before trying to use it.
  432. {
  433. // We have a problem in that as we execute this code, another thread might have just started executing the
  434. // the weWereHere = true pathway above. So the magic value might not have been written yet. We don't currently
  435. // have an easy means to deal with this other than to loop for N milliseconds while waiting for the other
  436. // thread to complete.
  437. // Another potential problem can occur if two threads of differing priorities execute on the same CPU and
  438. // one blocks the other from completing this code. That would be a
  439. int protection = 0;
  440. result = sceKernelQueryMemoryProtection(addr, NULL, NULL, &protection);
  441. if((result == SCE_OK) && (protection & SCE_KERNEL_PROT_CPU_READ) && (protection & SCE_KERNEL_PROT_CPU_WRITE))
  442. {
  443. EA::StdC::LimitStopwatch limitStopwatch(EA::StdC::Stopwatch::kUnitsMilliseconds, 500, true);
  444. while(!limitStopwatch.IsTimeUp())
  445. {
  446. EAReadBarrier(); // Make sure we see the previous writes of other threads prior to the read below.
  447. if(memcmp(kMagic, pOSGlobalManagerContainer->mMagicNumber, sizeof(pOSGlobalManagerContainer->mMagicNumber)) == 0) // If it's our memory...
  448. {
  449. // At this point we were able to create a new shared memory area or acquire the shared memory area that
  450. // some other InitOSGlobalSystem function call previously created.
  451. EA::StdC::AtomicIncrement(&pOSGlobalManagerContainer->mOSGlobalManager.mRefCount);
  452. gpOSGlobalManager = &pOSGlobalManagerContainer->mOSGlobalManager;
  453. EAWriteBarrier(); // This isn't strictly needed, but it can theoretically make things go faster for other threads.
  454. break;
  455. }
  456. // Else either the other thread is still initializing pOSGlobalManagerContainer or some other completely
  457. // unrelated entity is using this memory for something else. We don't have a good means of detecting
  458. // the latter other than timing out. Maybe if sceKernelMapDirectMemory guarantees writing 0 to the memory
  459. // then we can exit this loop much faster.
  460. // There must be another thread executing the weWereHereFirst = true pathway. We sleep so that thread
  461. // can complete that pathway. Possibly are are a higher priority thread which could be blocking it.
  462. SceKernelTimespec ts = { 0, 2000000 };
  463. sceKernelNanosleep(&ts, NULL);
  464. }
  465. }
  466. }
  467. }
  468. }
  469. // Try another address. The logic above has determined that the address was used by some other entity.
  470. // That may well indicate that we need to pick a new starting address to try, as we really want this to
  471. // always succeed the first time through. Note that this bumping up is safe and works as intended, because
  472. // if one module fails to work at the original address, all will (assuming that during startup some thread
  473. // doesn't map it before we try to get it and then later unmap it before the other modules that use this have loaded).
  474. currentAddr -= (1024 * 1024);
  475. } // while ...
  476. if(!gbKeepDirectMemory)
  477. {
  478. sceKernelReleaseDirectMemory(gDirectMemoryStartAddr, kDirectMemoryLength);
  479. gDirectMemoryStartAddr = 0;
  480. }
  481. } // if(!gpOSGlobalManager)
  482. if(gpOSGlobalManager) // (We have an AddRef on gpOSGlobalManager from above, so gpOSGlobalManager can't possibly have become invalid at this point)
  483. {
  484. // gOSGlobalRefs measures the number of times InitOSGlobalSystem/ShutdowOSGlobalSystem was successfully called.
  485. EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
  486. EA::StdC::AtomicIncrement(&gOSGlobalRefs); // Increment it once for the init of this system (InitOSGlobalSystem). This increment will be matched by a decrement in ShutdownOSGlobalSystem.
  487. return true;
  488. }
  489. return false;
  490. }
  491. void ShutdownOSGlobalSystem()
  492. {
  493. // gOSGlobalRefs measures the number of times InitOSGlobalSystem/ShutdowOSGlobalSystem was successfully called.
  494. if(EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
  495. {
  496. if(gpOSGlobalManager)
  497. {
  498. if(EA::StdC::AtomicDecrement(&gpOSGlobalManager->mRefCount) == 0) // mRefCount measures the use count of glOSGlobalManager.
  499. {
  500. // To consider: we can unmap the memory at gpOSGlobalManager - 16 bytes here. It doesn't buy us anything
  501. // aside from possibly making some tools see that we freed the mapped kernel memory.
  502. }
  503. if(gbKeepDirectMemory)
  504. {
  505. sceKernelReleaseDirectMemory(gDirectMemoryStartAddr, kDirectMemoryLength);
  506. gDirectMemoryStartAddr = 0;
  507. }
  508. gpOSGlobalManager = NULL;
  509. }
  510. }
  511. }
  512. #elif EASTDC_EAGLOBAL_UNIX
  513. namespace {
  514. #define EA_GLOBAL_UNIQUE_NAME_FORMAT "/SingleMgrMutex%llu"
  515. OSGlobalManager* CreateOSGlobalManager();
  516. bool InitOSGlobalSystem();
  517. void ShutdownOSGlobalSystem();
  518. OSGlobalManager* CreateOSGlobalManager()
  519. {
  520. // Allocate the OSGlobal manager in shared memory.
  521. #if defined(__APPLE__)
  522. void* pMemory = mmap(NULL, sizeof(OSGlobalManager), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
  523. #else
  524. void* pMemory = mmap(NULL, sizeof(OSGlobalManager), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  525. #endif
  526. if(pMemory) // Some Unix variants (e.g. mobile) can fail this call due to lack of support for it.
  527. {
  528. EA_ASSERT(((uintptr_t)pMemory & 15) == 0); // Make sure mmap returns at least 16 byte alignment.
  529. // Placement-new the global manager into the new memory.
  530. return new(pMemory) OSGlobalManager;
  531. }
  532. return NULL;
  533. }
  534. void DestroyOSGlobalManager(OSGlobalManager* pOSGlobalManager)
  535. {
  536. if(pOSGlobalManager)
  537. {
  538. gpOSGlobalManager->~OSGlobalManager();
  539. munmap(pOSGlobalManager, sizeof(OSGlobalManager));
  540. }
  541. }
  542. bool InitOSGlobalSystem()
  543. {
  544. // The following check is not thread-safe. On most platforms this isn't an
  545. // issue in practice because this function is called on application startup
  546. // and other threads won't be active. The primary concern is if the
  547. // memory changes that result below are visible to other processors later.
  548. if(!gpOSGlobalManager)
  549. {
  550. // We make a process-unique name based on the process id.
  551. char uniqueName[96];
  552. pid_t processID = getpid();
  553. EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned long long)processID);
  554. sem_t* mutex = sem_open(uniqueName, O_CREAT, 0644, 1); // Unix has named semaphores but doesn't really have named mutexes, so we use a semaphore as a mutex.
  555. if(mutex == SEM_FAILED)
  556. return false;
  557. if(sem_wait(mutex) == 0) // If locking the mutex was successful...
  558. {
  559. // As of this writing, we are using getenv/setenv to write a shared variable pointer. It turns out that this
  560. // is not a good idea, because getenv/setenv is not thread-safe. getenv returns a pointer to static memory
  561. // which another thread (who isn't using our mutex) might call setenv in a way that changes that memory.
  562. // The opinion of the Linux people is that you just shouldn't ever call setenv during application runtime.
  563. // A better solution for us is to use shared mapped memory (shm_open(), mmap()): http://www.ibm.com/developerworks/aix/library/au-spunix_sharedmemory/index.html
  564. const char* pName = getenv(uniqueName);
  565. if(pName && pName[0]) // If the variable was found...
  566. gpOSGlobalManager = (OSGlobalManager*)(uintptr_t)strtoull(pName, NULL, 16);
  567. else
  568. {
  569. gpOSGlobalManager = CreateOSGlobalManager();
  570. if(gpOSGlobalManager)
  571. {
  572. char buffer[32];
  573. EA::StdC::Sprintf(buffer, "%I64x", (uint64_t)(uintptr_t)gpOSGlobalManager);
  574. /*int result =*/ setenv(uniqueName, buffer, 1);
  575. }
  576. }
  577. if(gpOSGlobalManager)
  578. {
  579. EA_ASSERT(gpOSGlobalManager->mRefCount < UINT32_MAX);
  580. EA::StdC::AtomicIncrement(&gpOSGlobalManager->mRefCount);
  581. }
  582. sem_post(mutex);
  583. sem_close(mutex);
  584. sem_unlink(uniqueName);
  585. }
  586. if(!gpOSGlobalManager)
  587. {
  588. ShutdownOSGlobalSystem();
  589. return false;
  590. }
  591. EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
  592. EA::StdC::AtomicIncrement(&gOSGlobalRefs); // Increment it once for the init of this system (InitOSGlobalSystem). This increment will be matched by a decrement in ShutdownOSGlobalSystem.
  593. }
  594. return true;
  595. }
  596. void ShutdownOSGlobalSystem()
  597. {
  598. if(EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
  599. {
  600. if(gpOSGlobalManager)
  601. {
  602. if(EA::StdC::AtomicDecrement(&gpOSGlobalManager->mRefCount) == 0)
  603. DestroyOSGlobalManager(gpOSGlobalManager);
  604. gpOSGlobalManager = NULL;
  605. }
  606. // Clear the gpOSGlobalManager environment variable.
  607. // This code needs to be called in a thread-safe way by the user, usually by calling it once on shutdown.
  608. // We have a problem if this function is executing at the same time some other entity in this process
  609. // is currently doing some new use of the OS global system, as that can cause an instance of gpOSGlobalManager
  610. // to be created while we are in the process of destroying it.
  611. char uniqueName[96]; uniqueName[0] = 0;
  612. pid_t processID = getpid();
  613. EA::StdC::Sprintf(uniqueName, EA_GLOBAL_UNIQUE_NAME_FORMAT, (unsigned long long)processID);
  614. /*int result =*/ unsetenv(uniqueName);
  615. }
  616. }
  617. } // namespace
  618. #else // #if defined(EA_PLATFORM_MICROSOFT)
  619. namespace {
  620. static uint64_t sOSGlobalMgrMemory[(sizeof(OSGlobalManager) + 1) / sizeof(uint64_t)];
  621. bool InitOSGlobalSystem()
  622. {
  623. // Theoretical problem: If you keep calling this function, eventually gOSGlobalRefs will overflow.
  624. EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
  625. if(EA::StdC::AtomicIncrement(&gOSGlobalRefs) == 1)
  626. gpOSGlobalManager = new(sOSGlobalMgrMemory) OSGlobalManager;
  627. return true;
  628. }
  629. void ShutdownOSGlobalSystem()
  630. {
  631. if(EA::StdC::AtomicDecrement(&gOSGlobalRefs) == 0) // If the (atomic) integer decrement results in a refcount of zero...
  632. gpOSGlobalManager = NULL;
  633. }
  634. }
  635. #endif // #if defined(EA_PLATFORM_MICROSOFT)
  636. EASTDC_API EA::StdC::OSGlobalNode* EA::StdC::GetOSGlobal(uint32_t id, OSGlobalFactoryPtr pFactory)
  637. {
  638. // Initialize up the OSGlobal system if we are getting called before
  639. // static init, i.e. allocator
  640. if (!InitOSGlobalSystem())
  641. return NULL;
  642. gpOSGlobalManager->Lock();
  643. EA::StdC::OSGlobalNode* p = gpOSGlobalManager->Find(id);
  644. if (!p && pFactory)
  645. {
  646. p = pFactory();
  647. p->mOSGlobalID = id;
  648. AtomicSet(&p->mOSGlobalRefCount, 0);
  649. gpOSGlobalManager->Add(p);
  650. }
  651. if (p)
  652. {
  653. EA_ASSERT(p->mOSGlobalRefCount < UINT32_MAX);
  654. AtomicIncrement(&p->mOSGlobalRefCount);
  655. EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
  656. AtomicIncrement(&gOSGlobalRefs);
  657. }
  658. gpOSGlobalManager->Unlock();
  659. return p;
  660. }
  661. EASTDC_API bool EA::StdC::SetOSGlobal(uint32_t id, EA::StdC::OSGlobalNode *p)
  662. {
  663. // Initialize up the OSGlobal system if we are getting called before
  664. // static init, i.e. allocator
  665. if (!InitOSGlobalSystem())
  666. return false;
  667. gpOSGlobalManager->Lock();
  668. EA::StdC::OSGlobalNode* const pTemp = gpOSGlobalManager->Find(id);
  669. if (pTemp == NULL) // If there isn't one already...
  670. {
  671. p->mOSGlobalID = id;
  672. AtomicSet(&p->mOSGlobalRefCount, 0);
  673. gpOSGlobalManager->Add(p);
  674. EA_ASSERT(p->mOSGlobalRefCount < UINT32_MAX);
  675. AtomicIncrement(&p->mOSGlobalRefCount);
  676. EA_ASSERT(gOSGlobalRefs < UINT32_MAX);
  677. AtomicIncrement(&gOSGlobalRefs);
  678. }
  679. gpOSGlobalManager->Unlock();
  680. return (pTemp == NULL);
  681. }
  682. EASTDC_API bool EA::StdC::ReleaseOSGlobal(EA::StdC::OSGlobalNode *p)
  683. {
  684. gpOSGlobalManager->Lock();
  685. const bool shouldDestroyManager = AtomicDecrement(&gOSGlobalRefs) == 0;
  686. const bool shouldDestroyOSGlobal = AtomicDecrement(&p->mOSGlobalRefCount) == 0;
  687. if (shouldDestroyOSGlobal)
  688. gpOSGlobalManager->Remove(p);
  689. gpOSGlobalManager->Unlock();
  690. // Note by Paul Pedriana (10/2009): It seems to me that shouldDestroyManager will never
  691. // be true here because InitOSGlobalSystem will have been called at app startup and
  692. // its gOSGlobalRefs increment will still be live. So only when that last explicit
  693. // call to ShutdownOSGlobalSystem is called will gOSGlobalRefs go to zero.
  694. if (shouldDestroyManager)
  695. ShutdownOSGlobalSystem(); // This function decrements gOSGlobalRefs.
  696. return shouldDestroyOSGlobal;
  697. }
  698. // Force the OSGlobal manager to be available for the life of the app.
  699. // It's OK if this comes up too late for some uses because GetOSGlobal()
  700. // will bring it online earlier in that case.
  701. namespace
  702. {
  703. struct AutoinitOSGlobalManager
  704. {
  705. AutoinitOSGlobalManager()
  706. {
  707. bool result = InitOSGlobalSystem();
  708. EA_ASSERT(result); EA_UNUSED(result);
  709. }
  710. ~AutoinitOSGlobalManager()
  711. {
  712. ShutdownOSGlobalSystem();
  713. }
  714. };
  715. AutoinitOSGlobalManager gAutoinitOSGlobalManager;
  716. }
  717. #endif // EASTDC_GLOBALPTR_SUPPORT_ENABLED
  718. #if defined(EA_PLATFORM_MICROSOFT)
  719. #pragma warning(pop) // symmetric for pop for the above --> #pragma warning(disable: 4355) // warning C4355: 'this' : used in base member initializer list
  720. #endif