Consider reading these 2020 mac cleaner reviews and picking one software for yourself. The burden of a hard drive may reduce by transferring some files to cloud-based software, like Dropbox or iCloud. And these services are free, meaning that everyone can use them. Eventually, you can get Better CPU Performance on MAC.
MAC lung disease is an infection caused a group of bacteria called Mycobacterium avium complex (MAC). MAC includes two closely related species, Mycobacterium avium and Mycobacterium intracellulare, and may also be referred to as MAI.MAC is one of a large group of nontuberculous mycobacteria (NTM), and the most common cause of NTM lung disease in the U.S. And let’s be honest, the newly released OS X Server 3.0, which is only $20 in the Mac App Store, is a full-fledged server OS that’s as simplified and easy to use as OS X. Additional resources. 'The burden of two operating systems is mostly the sheer span of knowledge involved and the time available to study or play with them,' he says. 'Right now, I've got Mac OS X 10.6 and Windows 7. MAC is one of a large group of nontuberculous mycobacteria (NTM), and the most common cause of NTM lung disease in the U.S. MAC organisms are common in soil and water and are easily inhaled during daily activities. Most of the time they cause no harm, but they can cause infection in groups with certain risk factors.
It’s April 1, and that means it’s both April Fools’ Day and the anniversary of the founding of Apple Inc. While this year is a sober one due to current events, I think a lot of people still appreciate what people are creating and sharing to keep spirits up, whether that be music or art or…impractical programming projects. And while pranks on April Fools’ seem less and less fun1, obvious jokes and whimsy, not at anyone’s expense, are still something I believe in…and even better if they actually work.
Last year I implemented the world’s best code visualizer. This year I decided to seriously attempt something that I’d thought about in the past: getting a Swift program to run on Mac OS 9.
What’s a Mac OS 9?
Twenty (!) years ago, before the macOS2 we know today, there was another operating system known as “Mac OS”.3 It was one of the first OSs to use a GUI at all, something that we pretty much take for granted these days. It also dates from the days when only one program could run at a time; because of that, even the latest version uses cooperative multitasking to run multiple programs—that is, a program has to yield its time to let others run.4 If a program crashed or overwrote memory it wasn’t supposed to, there was a good chance you’d have to restart the whole system.
Mac OS 9 ran on PowerPC processors, which were also used in the GameCube, PS3, and Xbox 360; earlier versions of the OS had started on Motorola’s 68k CPU series. Its successor Mac OS X5 also ran on PowerPC when it first launched; it wasn’t until 10.4 that Apple began to switch to Intel processors instead, and 10.6 when PowerPC was finally dropped.
Mac OS X was a huge step forward from Mac OS 9 in a number of ways, including preemptive multitasking so that you could actually run multiple things at once. But Apple didn’t want to just leave OS 9 programs behind, so they did two things:
- The Classic environment set up a sandbox that looked enough like Mac OS 9 to run Classic Mac OS programs directly in Mac OS X. Because the Classic environment was itself an app, all the programs that ran inside it were protected from interfering with other Mac OS X programs and vice versa. It really was quite effective, and actually survived longer than booting into Mac OS 9 (which never received support for newer PowerPC processors). But its life ended with the switch to Intel-powered Macs—Classic was built on running the instructions in the original apps directly, only having to provide compatibility shims for libraries. Think of it like Wine / CrossOver rather than VirtualBox / Parallels.6
- Carbon was a packaged-up version of the old Mac OS Toolbox APIs so that you could write Mac OS X apps the same way you always had. You basically just recompiled your app and added an extra annotation saying you were “Carbonized”. (Sound familiar?7) Fun aside: This is the reason (one of the reasons?) Core Foundation exists—to provide a common interface between Carbon and Cocoa. (h/t Marshall Elfstrand for the video link.)
Classic ended with the switch to Intel processors back in the 2000s, but Carbon worked all the way up to last year, macOS Mojave. Apple never released a 64-bit version of Carbon, presumably to encourage developers to move to Cocoa, and with last year’s macOS Catalina, support for 32-bit apps was dropped entirely with very few exceptions.8
What’s the goal?
Since I learned to program on Classic Mac OS, and years later spent a good chunk of my career working on Swift, I’ve had the tantalizing thought that I’d like to write a program in Swift and run it on Mac OS 9. That is,
- I write Swift source code that calls Carbon / Toolbox APIs.
- I compile it for PowerPC with (a version of) the Swift compiler.
- I package it up as necessary for Mac OS 9.
- Profit!
Is this useful? No! Absolutely not! But neither was ROSE-8, and yet I still learned a lot doing it.
As you probably guessed, I managed to accomplish this, or I wouldn’t be writing this blog post. So, without further ado, here’s a picture of a Swift Toolbox app running on Mac OS 9.2, on my friend Nadine’s Power Mac G4. (Check out that blazing fast 400MHz processor!)
I assume a good number of people reading this would like to know how to do it too!
- If you want to build your own PPC-capable Swift compiler, check out the following repositories:Note the directory and branch names of the sub-repos, and note that they should be nested inside the ppc-swift-project repo. You will also need the
mpw
emulator and a copy of the Macintosh Programmer’s Workshop tools9 to build an actual app using modern macOS. - If you want a prebuilt PPC-capable Swift toolchain, here’s one: ppc-swift-toolchain. Note that while I’ve put a built Swift.o in this toolchain, you’ll probably only have success with optimized code that doesn’t actually have any remaining links to the stdlib (i.e. everything is inlined away). You will also need the
mpw
emulator and a copy of the Macintosh Programmer’s Workshop tools.9You may still want to check out the example in the ppc-swift-project repo. The required flags forswiftc
,PPCLink
, andRez
can be a little finicky. (And note that theSIZE
andcarb
resources are required for any Carbon app, so you can’t just skip the Rez part if you actually want to run your app.) - If you just want to try a built version of BitPaint, here’s one: BitPaint-swift.hqx.
I’d like to hear about anything you make with these tools! Meanwhile, if you’d like to hear how I made this work, read on.
Gathering materials
The last time I was building Classic Mac OS apps, I was using CodeWarrior. Actually, calling that “building Classic Mac OS apps” was a stretch; I was learning C and using CodeWarrior’s terminal I/O library to get a stdin/stdout interface that Classic Mac OS didn’t have natively. (Remember, no command line!) I could try to get some version of CodeWarrior running again, but that didn’t seem like the most convenient thing. I didn’t think I’d be able to get the Swift compiler running on Classic, so I’d be shuttling object files back and forth between OSs to get anything done.
Fortunately for me, I’m not the only one interested in building Classic apps on modern macOS. At some point I found about the
mpw
project: an emulator specifically for running Apple’s Macintosh Programmer’s Workshop tools. And I knew it was going to work, too, because Steve Troughton-Smith, (in)famous in the Apple community for finding undocumented and prerelease features in Apple’s OSs, had written up his experiences building an app with mpw
that ran on System 1 all the way up to modern Mac OS X, just by building with the appropriate compiler and against the appropriate libraries.If you’re interested in all this, I highly recommend checking out his blog post. Not only was this the reference I used to get started, but the app you see running in the above picture, BitPaint, is Troughton-Smith’s test app, ported to Swift. (I did ask him ahead of time if it was okay to use his app for a hobbyist project.) Longtime Mac developer Gwynne Raskind also gave a two-part high-level tour of the Toolbox APIs on Mike Ash’s blog several years ago (part 1 | part 2); fortunately, Carbon takes care of a fair amount for us even on Mac OS 9.
So okay. What does MPW give us?
- A PowerPC compiler
- A PowerPC assembler
- A PowerPC linker
- The Classic Mac OS header files
- The Classic Mac OS library stubs, for linking against
- A bunch of object and binary inspection tools, which we don’t need for the finished product but which I made a lot of use of when trying to debug mystery misbehavior
That’s pretty good; as Troughton-Smith’s blog post shows, it’s enough to build an entire app that’ll run on Classic. My idea was to take object files produced by a modern compiler and feed them to the PowerPC linker, which means I’ll additionally need:
- A modified version of the Swift compiler that supports emitting MPW-compatible object files
- Some stripped-down form of the Swift standard library and runtime (enough to read in and interact with Carbon headers, at least)
- An actual machine running Mac OS 9. I have one, but not the charger for it, and so I did most of my testing using SheepShaver. My friend Nadine provided some testing on actual machines once things were working.
And, well, that should be it! So, off we go.
Modern compiler, classic linker
To make things more manageable, I set an intermediate goal: build an app using Clang, the modern C compiler that ships with Xcode. Clang uses the same LLVM infrastructure as the Swift compiler, so I figured I could deal with all the object format and workflow issues in Clang, and then move on to the Swift-specific parts.
The first thing I did was try to figure out what the file format was for PowerPC object files. It turns out it’s a format called XCOFF; searching for modern documentation on this turned up an IBM reference doc. Pretty much no one else uses this format, which was not encouraging. The first time I started looking into this project, I was worried I’d have to have my compiler write out assembly code and then send that through the MPW PowerPC assembler…after fixing it up to account for the differences in how LLVM and MPW print PowerPC assembly.
However, when I checked to see if LLVM supported XCOFF, I was in for a stroke of luck. It turns out IBM has started adding support for XCOFF to LLVM just last year, as part of adding support for their AIX OS…which runs on PowerPC. So I could ask Clang to generate XCOFF files for AIX, which means it should only be a short step to making it generate XCOFF files for Classic Mac OS.
At this point I remembered a bit of trivia. Apple and IBM used to have a close partnership, along with Motorola. They’d even made some common standards that were used across platforms and CPUs, though perhaps with less impact than they’d hoped. Was it possible that AIX and Classic Mac OS used the same calling conventions for their procedures, and they could just interoperate without any extra work?
I got lucky: the answer is (nearly) yes. The AIX register conventions and stack conventions match up with the ones in the Mac OS Runtime Architectures guide. That meant I could feed object files produced by Clang directly into MPW’s
PPCLink
and get a working Classic Mac OS binary out.I’m pretty sure my mouth fell open when I first saw this work.
That should work with just top-of-master-branch LLVM/Clang (and a very simple test.c). I did end up needing to change LLVM in a few ways in the end, but it’s really fairly minimal, so much thanks to the IBM folks for doing the hard part of the work for me!
Now do Swift
Being able to compile a simple test program was a great milestone, but I had to do a fair bit of work before I could get swiftc to compile a whole BitPaint. Here are some of the highlights:
- Teach Swift about the PPC/AIX target. This mostly involved adding
ppc
andAIX
cases to switch statements across the Swift compiler, but also involved making a simple description of the Swift calling conventions for Clang (which I cribbed from the 32-bit ARM implementation) and then assuring the PPC/AIX backend that this was an okay calling convention to be using. I got lucky in the amount of work I had to do here because Swift already supports 32-bit ARM, little-endian 64-bit PowerPC (when running Linux), and big-endian 64-bit s390x (another IBM architecture); all the pieces were already in place. - Add support for Pascal strings. The Mac’s first high-level programming language was Pascal, not C! As such, the default format for strings throughout the Toolbox APIs was Pascal strings (a length byte followed by string data) rather than C strings (string data followed by a null byte). With the
-fpascal-strings
command-line flag, Clang supports static Pascal strings with the syntax'pHello World'
. Thep
would be replaced by the length of the string (which must be no more than 255 bytes) so that you didn’t have to count it yourself. I hacked this into Swift as well, and while my implementation probably has problems10, it was enough to get simple things working. - Turn off reflection support and nearly all runtime metadata. The Swift runtime is very powerful, but I didn’t want to write much of a runtime for this project, which was primarily about calling a bunch of C functions. Beyond that, though, the default format for Swift metadata makes heavy use of relative addressing (mainly to reduce startup time, but learn more here) as well as symbols pointing inside of a global, and the LLVM XCOFF implementation doesn’t (yet?) support either. So to get to a working proof-of-concept, I aggressively commented out parts of IRGen that made use of either feature. I’d like to get some of the static metadata back at some point, but reflection’s not something I’m ever interested in. Probably.
- Make a smol stdlib. The full Swift standard library has a lot of things in it I don’t need, and some that I wouldn’t even know how to implement. (What’s a String in a world that can’t assume Unicode?) But all the logic to integrate with C code is based on having some basic types in the standard library (like Int16 and UnsafeMutablePointer). What I ended up doing was taking a subset of the standard library sources, and then adding additional files and commenting things out until it worked.…haha, nope, even that wasn’t good enough. My early attempts at this compiled okay, but they managed to crash PPCLink when I tried to write a test program, presumably because there’s just too many symbols in the standard library. So I cut things down to an even smaller subset, and that (eventually) worked. Of course, I was working on this at the same time as I was modifying the compiler to get to a working proof-of-concept, so I think I ultimately went further than I needed to. (A bunch of symbols are only used for runtime metadata purposes.) As mentioned above, non-optimized builds of non-trivial programs don’t work yet, so I don’t know if I’m in the danger zone or not, but I might try to add a few more things back in.Rather than modify the actual Swift repo for this, I decided to keep my stripped-down standard library separate, so you can find it in the ppc-swift-project repo. This might be a good reference for someone looking for a C-compatible, runtime-less subset of Swift, perhaps for an embedded or other resource-constrained environment.
- Disable jump tables. LLVM optimizes switch statements into jump tables when it looks like it’ll help performance and/or code size, but its default implementations of jump tables also weren’t supported in the LLVM XCOFF implementation. I imagine the AIX folks will get around to implementing this sooner or later, but for now I just disabled jump tables entirely, forcing the compiler to emit switches as a series of
if
s instead.
You can check out all the changes in the swift and llvm-project repos, if you’re curious. Very few are appropriate for upstreaming to their respective projects, but I’ll try to get the ones that are relevant upstreamed at some point.
A week of mysterious failures
![Mac Mac](https://meraki.cisco.com/blog/wp-content/uploads/2013/10/Mac-OS-Profile.png)
Having made all the changes above, I had an app that worked! In Swift!
…except, it only worked some of the time. I’d change something arbitrary and suddenly events wouldn’t register any more. It got so bad that I added a counter: after any ten events, exit the app. Without that, I’d get trapped, unable to even quit without restarting the (virtual) machine. Even in a seemingly working version, my friend Nadine reported that trying to use the Reset command caused the app to crash. What was going on?
I decided I had to get to the bottom of something strange I’d seen earlier: even the Clang version of the program didn’t work correctly when I turned on optimizations. It’s possible that that was a bug in IBM’s newly-added AIX support, or 32-bit PowerPC support since it’s not such a common platform, or even LLVM’s optimizations. It could be that AIX and Classic Mac OS really weren’t as similar as I thought they were, and so my code wasn’t agreeing with the system code on how things were supposed to work. And it could be that the optimized code was using an instruction that SheepShaver didn’t support, though that didn’t really seem to match the symptoms.
And the symptoms were weird. Some local variables were getting corrupted, but others weren’t. So I started testing everything I could think of:
- was the stack aligned properly?
- was the stack pointer somehow not getting restored properly?
- was the glue code for cross-library calls trashing other data?11 (see the Mac OS Runtime Architectures guide)
- was there something causing the Code Fragment Manager (dynamic linker) to put the wrong address in for cross-library calls?
Without being able to rely on logging, I made the simplest textual debug output facility I could: modifying the title of a menu. (It later turned out that writing to stdout in Mac OS 9 automatically results in a file being created, so I could have used that instead.) I wrote C functions that tracked the current stack pointer to make sure it was getting restored properly; I made good use of the
DumpXCOFF
and DumpPEF
tools that came with MPW; I learned how PEF “pidata” (“pattern-initialized data”) worked and tried to step through CFM relocations by hand (again, see the Mac OS Runtime Architectures guide). I even started trying to decompile some of the actual system libraries to see if they were doing anything suspicious, even though a bug in the actual Mac OS 9 seemed incredibly unlikely. This led all the way to learning about the “toolbox ROM”, which isn’t actually ROM at all: it’s a boot script and a compressed set of system libraries. (It’s called that because it’s content that used to be in ROM.) Fortunately SheepShaver already knows how to load it, which meant that I could do the same decompression and then manually split out the individual libraries.Yeah, I got way off in the weeds. I learned a lot, though!
Finally, I looked at the decompiled optimized code—the C version, not the Swift version. I observed that the variable getting corrupted was in general-purpose register 13. That’s supposed to be an okay place to put data in Classic Mac OS (and in 32-bit AIX, and in 32-bit Mac OS X), but I decided I didn’t trust that, particularly because that register had been used to track thread-local storage in 64-bit AIX. So I marked r13 as reserved…
…and the problems went away. Optimized, non-optimized, even with
-fstack-protector-all
on. And Swift.(Debugging this took about a week, unfortunately, which led to this project being a little less ambitious than I originally wanted.)
“Future directions”
What didn’t I get to? An awful lot, actually.
- There’s no runtime at all, which means no dynamic allocation (among other things).
- There’s no type metadata, which means no generics (that aren’t optimized away).
- There’s no field metadata, which means no key paths (that aren’t optimized away).
- There’s no Unicode support, so no Strings. Arguably I could make a String without Characters, or a String using MacRoman as the native encoding, but it wouldn’t necessarily look much like today’s Swift.String.
- There are a bunch of other standard library things missing because I wanted to get the proof-of-concept working, but also because
PPCLink
was choking on large object files. If I do get more standard library stuff working, I’ll probably split it out of the ‘Swift’ module somehow. - I had to mess with linkage in a number of ways to make the LLVM XCOFF backend happy, so I’m not sure multi-file builds would work. I didn’t even test it.
- I’m using Carbon, which means that my program ought to work on older versions of Mac OS X as well, but my friend Nadine tried and it didn’t, and it wasn’t a priority to figure out.
- I wanted to try making nice abstractions on top of the some of the Toolbox APIs.
- I wanted to make more complicated example apps!
Maybe I’ll follow up on some of these, but I’ve been putting a lot of effort into making sure I could finish this by April 1, so I should probably get to some of the things I’ve been neglecting in favor of this project instead.
Summary
This project took a lot of time, even though I (1) know a lot about compilers and (2) hacked my way to success instead of being careful and maintaining proper software development practices. But I learned a lot, and I accomplished a goal I’ve had in the back of my mind for a long time.
If you made it all the way to the end of the article, here’s a reward: BitPaint running under Classic on Mac OS X 10.2 (also courtesy of Nadine).
Stay safe, everyone, and help the people around you when you can. And if anybody makes something with this project, I want to hear about it!
- Née OS X, née Mac OS X. ↩︎
- Née “System”, as in “System 7”. The OS only got branded in Mac OS 7.6. ↩︎
- If you’re interested in hearing more about this, check out the MultiFinder article on Wikipedia. ↩︎
- Apple did also make an emulator for PowerPC apps when they switched to Intel chips, called Rosetta. In a way this was an even more technically impressive feat than Classic, but why couldn’t they run Classic through Rosetta? The Wikipedia article suggests that it’s because Classic required lower-level system hooks that they didn’t want to provide in Rosetta; I could also imagine it being too many layers to get good performance through, or even just Apple trying to shed the maintenance burden of a piece of software that, in general, was getting less and less use each year. ↩︎
- While I couldn’t resist the opportunity to make a technical comparison to Catalyst, I don’t think the situations are really that similar. For a Mac programmer, Carbon/Toolbox would have been the familiar API then, but for a Mac programmer, AppKit is the familiar API now, and UIKit is the newly-introduced thing. ↩︎
- “This has made a lot of people very angry and been widely regarded as a bad move.” The problem here isn’t just with developers needing to port their existing apps to 64-bit (or to Cocoa, if they were still on Carbon even after it stopped receiving updates); it’s a concern with older apps whose developers have no plans to update them. People are especially concerned about games, which can’t be “replaced” by a similar app that handles the same data. On the other hand, there’s a lot of older code in the OS that Apple no longer has to support, which means fewer security vulnerabilities, fewer bugs, and faster development. In theory, anyway. Meanwhile, people are resorting to bizarre things like running macOS Mojave in a virtual machine, which I regret to inform you totally works, mostly. ↩︎
- Actually getting MPW these days is increasingly tricky. I’m not comfortable hosting it myself, and the still-available hosting linked at the bottom of the Wikipedia page contains the tools in the form of an HFS disk image—the disk format Apple used beforeHFS+, which has itself been superseded by APFS. HFS disk images are no longer supported on macOS 10.15 Catalina, so to extract the disk image I ended up using my Mac OS 9 install and a hastily-obtained install of Disk Copy, which for some reason Apple still hosts. If you have a pre-Catalina machine around, that’s probably easier. ↩︎↩︎2
- Swift strings are supposed to be valid UTF-8, and I’m not sure if some part of the compiler will choke if they’re not. But if I ever have a string literal that’s longer than 127 bytes, a length byte is going to show up as part of a multibyte UTF-8 sequence rather than a single Unicode scalar. Fortunately all my test strings have been short so far.(Strings on Classic Mac OS were encoded as MacRoman by default anyway, so I’d also run into this problem if I tried to, say, put a MacRoman ellipsis character in a static string.) ↩︎
- As an aside, the code for cross-library calls (“named indirect calls”) seems like it ought to be sharing logic for calls through a function pointer. That would decrease code size at the cost of one extra jump, but maybe that one extra jump has a significant impact on performance.12↩︎
- This post sets a record for “number of footnotes on any post I’ve written” (even without this one). It was suggested I commemmorate that with a footnote. ↩︎
The fundamental services and primitives ofthe OS X kernel are based on Mach3.0. Apple has modified and extended Mach to better meet OS X functional and performance goals.
Mach 3.0 was originally conceived as a simple, extensible,communications microkernel. It iscapable of running as a stand–alone kernel, with other traditionaloperating-system services such as I/O, file systems, and networkingstacks running as user-mode servers.
However, in OS X, Mach is linked with other kernel componentsinto a single kernel address space. This is primarily for performance;it is much faster to make a direct call between linked componentsthan it is to send messages or do remote procedure calls (RPC) betweenseparate tasks. This modular structure results in a more robustand extensible system than a monolithic kernel would allow, withoutthe performance penalty of a pure microkernel.
Thus in OS X, Mach is not primarily a communication hubbetween clients and servers. Instead, its value consists of itsabstractions, its extensibility, and its flexibility. In particular,Mach provides
- object-basedAPIs with communication channels (for example, ports) as object references
- highly parallel execution, including preemptively scheduled threads and support for SMP
- a flexible scheduling framework, with support for real-time usage
- a complete set of IPC primitives, including messaging, RPC, synchronization, and notification
- support for large virtual address spaces, shared memoryregions, and memory objects backed by persistent store
- proven extensibility and portability, for example across instructionset architectures and in distributed environments
- security and resource management as a fundamental principleof design; all resources are virtualized
Mach Kernel Abstractions
Mach provides a small set of abstractions that have been designedto be both simple and powerful. These are the main kernel abstractions:
- Tasks. Theunits of resource ownership; each task consists of a virtual addressspace, a portrightnamespace, and one or more threads.(Similar to a process.)
- Threads. The units of CPU execution withina task.
- Addressspace. In conjunction with memory managers, Mach implementsthe notion of a sparse virtual address space and shared memory.
- Memoryobjects. The internal units of memory management. Memoryobjects include named entries and regions; they are representationsof potentially persistent data that may be mapped into address spaces.
- Ports.Secure, simplex communication channels, accessible only via sendand receive capabilities (known as port rights).
- IPC.Message queues, remote procedure calls, notifications, semaphores,and lock sets.
- Time.Clocks, timers, and waiting.
At the trap level, the interface to most Mach abstractionsconsists of messages sent to and from kernel ports representingthose objects. The trap-level interfaces (such as
mach_msg_overwrite_trap
)and message formats are themselves abstracted in normal usage bythe Mach Interface Generator(MIG).MIG is used to compile procedural interfaces to the message-basedAPIs, based on descriptions of those APIs.Tasks and Threads
OS X processes and POSIXthreads (pthreads)are implemented on top of Mach tasks and threads, respectively.A thread is a point of control flow in a task. A task exists to provideresources for the threads it contains. This split is made to providefor parallelism and resource sharing.
A thread
- is a pointof control flow in a task.
- has access to all of the elements of the containing task.
- executes (potentially) in parallel with other threads, eventhreads within the same task.
- has minimal state information for low overhead.
A task
- is a collectionof system resources. These resources, with the exception of theaddress space, are referenced by ports. These resources may be sharedwith other tasks if rights to the ports are so distributed.
- provides a large, potentially sparse address space, referencedby virtual address. Portions of this space may be shared throughinheritance or external memory management.
- contains some number of threads.
![The burden mac os catalina The burden mac os catalina](https://i0.wp.com/www.appletips.nl/wp-content/uploads/2013/07/muisaanwijzer.png?resize=600%2C428&ssl=1)
Note that a task has no life of its own—only threads executeinstructions. When it is said that “task Y does X,” what isreally meant is that “a thread contained within task Y does X.”
A task is a fairly expensive entity. It exists to be a collectionof resources. All of the threads in a task share everything. Twotasks share nothing without an explicit action (although the actionis often simple) and some resources (such as port receive rights) cannotbe shared between two tasks at all.
A thread is a fairly lightweight entity. It is fairly cheapto create and has low overhead to operate. This is true becausea thread has little state information (mostly its register state). Itsowning task bears the burdenof resource management. On a multiprocessor computer, it is possiblefor multiple threads in a task to execute in parallel. Even whenparallelism is not the goal, multiple threads have an advantagein that each threadcan use a synchronous programming style, instead of attempting asynchronousprogramming with a single thread attempting to provide multipleservices.
A threadis the basic computational entity. A thread belongs to one and onlyone task that defines its virtual address space. To affect the structureof the address space or to reference any resource other than theaddress space, the thread must execute a special trap instructionthat causes the kernel to perform operations on behalf of the threador to send a message to some agent on behalf of the thread. In general,these traps manipulate resources associated with the task containingthe thread. Requests can be made of the kernel to manipulate theseentities: to create them, delete them, and affect their state.
Mach provides a flexible framework for thread–schedulingpolicies. Early versions of OS X support both time-sharing and fixed-priority policies.A time-sharing thread’s priority is raised and lowered to balanceits resource consumption against other time-sharing threads.
Fixed-priority threads execute for a certain quantum of time, and then areput at the end of the queue of threads of equal priority. Settinga fixed priority thread’s quantum level to infinity allows thethread to run until it blocks, or until it is preempted by a threadof higher priority. High priority real-time threads are usuallyfixed priority.
OS X also provides time constraint scheduling for real-timeperformance. This scheduling allows you to specify that your threadmust get a certain time quantum within a certain period of time.
Mach scheduling is described further in Mach Scheduling and Thread Interfaces.
Ports, Port Rights, Port Sets,and Port Namespaces
With the exception of the task’s virtual address space,all other Mach resources are accessed through a level of indirectionknown as a port.A port is an endpoint of a unidirectional communication channelbetween a client who requests a service and a server who providesthe service. If a reply is to be provided to such a service request,a second port must be used. This is comparable to a (unidirectional)pipe in UNIX parlance.
In most cases, the resource that is accessed by the port (thatis, named by it) is referred to as an object. Most objects namedby a port have a single receiver and (potentially) multiple senders.That is, there is exactly one receive port, and at least one sendingport, for a typical object such as a message queue.
The service to be provided by an object is determined by themanager that receives the request sent to the object. It followsthat the kernel is the receiver for ports associated with kernel-providedobjects and that the receiver for ports associated with task-provided objectsis the task providing those objects.
For ports that name task-provided objects, it is possibleto change the receiver of requests for that port to a differenttask, for example by passing the port to that task in a message. Asingle task may have multiple ports that refer to resources it supports.For that matter, any given entity can have multiple ports that representit, each implying different sets of permissible operations. Forexample, many objects have a name port anda controlport (sometimes called the privileged port).Access to the control port allows the object to be manipulated;access to the name port simply names the object so that you canobtain information about it or perform other non-privileged operationsagainst it.
Tasks have permissions to access ports in certain ways (send,receive, send-once); these are called port rights. A port can be accessed only via a right. Ports are often usedto grant clients access to objects within Mach. Having the rightto send to the object’s IPC port denotes the right to manipulatethe object in prescribed ways. As such, port right ownership isthe fundamental security mechanismwithin Mach. Having a right to an object is to have a capabilityto access or manipulate that object.
Port rights can be copied and moved between tasks via IPC. Doing so,in effect, passes capabilities to some object or server.
One type of object referred to by a port is a port set.As the name suggests, a port set is a set of port rights that canbe treated as a single unit when receiving a message or event fromany of the members of the set. Port sets permit one thread to waiton a number of message and event sources, for example in work loops.
Traditionally in Mach, the communication channel denoted bya port was always a queue of messages.However, OS X supports additional types of communication channels, andthese new types of IPC object are also represented by ports andport rights. See the section Interprocess Communication (IPC),for more details about messages and other IPC types.
Snakebutt mac os. Ports and port rights do not have systemwide names that allowarbitrary ports or rights to be manipulated directly. Ports canbe manipulated by a task only if the task has a port right in itsport namespace. A port right is specified by a port name, an integerindex into a 32-bit portnamespace. Each task has associated with it a single port namespace.
Tasks acquire port rights when another task explicitly insertsthem into its namespace, when they receive rights in messages, bycreating objects that return a right to the object, and via Machcalls for certain special ports (
mach_thread_self
, mach_task_self
,and mach_reply_port
.)Memory Management
As with most modern operating systems, Mach provides addressingto large, sparse, virtual address spaces. Runtime access is madevia virtual addresses that may not correspond to locations in physicalmemory at the initial time of the attempted access. Mach is responsiblefor taking a requested virtual address and assigning it a correspondinglocation in physical memory. It does so through demand paging.
A range of a virtual address space is populated with datawhen a memory object is mapped into that range. All data in an addressspace is ultimately provided through memory objects. Mach asks theowner of a memory object (apager)for the contents of a page when establishing it in physical memoryand returns the possibly modified data to the pager before reclaimingthe page. OS X includes two built-in pagers—the defaultpager and the vnode pager.
The default pager handles nonpersistent memory, known as anonymousmemory. Anonymous memory is zero-initialized, and it existsonly during the life of a task. The vnode pager maps files intomemory objects. Mach exports an interface to memory objects to allowtheir contents to be contributed by user-mode tasks. This interfaceis known as the External Memory Management Interface, or EMMI.
The memory management subsystem exports virtual memory handlesknown as named entries or namedmemory entries. Like most kernel resources, these aredenoted by ports. Having a named memory entry handle allows theowner to map the underlying virtual memory object or to pass theright to map the underlying object to others. Mapping a named entryin two different tasks results in a shared memory window betweenthe two tasks, thus providing a flexible method for establishingshared memory.
Beginning in OS X v10.1, the EMMI systemwas enhanced to support “portless” EMMI. In traditional EMMI,two Mach ports were created for each memory region, and likewise twoports for each cached vnode. Portless EMMI, in its initial implementation,replaces this with direct memory references (basically pointers).In a future release, ports will be used for communication with pagersoutside the kernel, while using direct references for communicationwith pagers that reside in kernel space. The net result of thesechanges is that early versions of portless EMMI do not support pagersrunning outside of kernel space. This support is expected to bereinstated in a future release.
Address ranges of virtual memory space may also be populatedthrough direct allocation (using
vm_allocate
).The underlying virtual memory object is anonymous and backed by thedefault pager. Shared ranges of an address space may also be setup via inheritance. When new tasks are created, they are clonedfrom a parent. This cloning pertains to the underlying memory addressspace as well. Mapped portions of objects may be inherited as acopy, or as shared, or not at all, based on attributes associatedwith the mappings. Mach practices a form of delayed copy known as copy-on-write tooptimize the performance of inherited copies on task creation.Rather than directly copying the range, a copy-on-write optimization is accomplishedby protected sharing. The two tasks share the memory to be copied,but with read-only access. When either task attempts to modify aportion of the range, that portion is copied at that time. Thislazy evaluation of memory copies is an important optimization thatpermits simplifications in several areas, notably the messaging APIs.
One other form of sharing is provided by Mach, through theexport of namedregions. A named region is a form of a named entry, butinstead of being backed by a virtual memory object, it is backedby a virtual map fragment. This fragment may hold mappings to numerousvirtual memory objects. It is mappable into other virtual maps,providing a way of inheriting not only a group of virtual memoryobjects but also their existing mapping relationships. This featureoffers significant optimization in task setup, for example when sharinga complex region of the address space used for shared libraries.
Interprocess Communication(IPC)
Communication between tasks is an important element of theMach philosophy. Mach supports a client/server system structurein which tasks (clients) access services by making requests of othertasks (servers) via messages sent over a communication channel.
The endpoints of these communication channels in Mach arecalled ports, while port rights denote permission to use the channel.The forms of IPC provided by Mach include
- messagequeues
- semaphores
- notifications
- lock sets
- remote procedure calls (RPCs)
The type of IPC object denoted by the port determines theoperations permissible on that port, and how (and whether) datatransfer occurs.
Important: The IPCfacilities in OS X are in a state of transition. In early versionsof the system, not all of these IPC types may be implemented.
There are two fundamentally different Mach APIs for raw manipulationof ports—the
mach_ipc
familyand the mach_msg
family.Within reason, both families may be used with any IPC object; however,the mach_ipc
calls arepreferred in new code. The mach_ipc
calls maintainstate information where appropriate in order to support the notionof a transaction. The mach_msg
callsare supported for legacy code but deprecated; they are stateless. IPC Transactions and EventDispatching
When a thread calls
mach_ipc_dispatch
,it repeatedly processes events coming in on the registered portset. These events could be an argument block from an RPCobject (as the results of a client’s call), a lock object beingtaken (as a result of some other thread’s releasing the lock),a notification or semaphore being posted, or a message coming infrom a traditional message queue. These events are handled via callouts from
mach_msg_dispatch
.Some events imply a transaction during the lifetime of the callout.In the case of a lock, the state is the ownership of the lock. Whenthe callout returns, the lock is released. In the case of remoteprocedure calls, the state is the client’s identity, the argumentblock, and the reply port. When the callout returns, the reply issent.When the callout returns, the transaction (if any) is completed,and the thread waits for the next event. The
mach_ipc_dispatch
facilityis intended to support work loops.Message Queues
Originally, the sole style of interprocess communication inMach was the messagequeue. Only one task can hold the receive right for a port denotinga message queue. This one task is allowed to receive (read) messagesfrom the port queue. Multiple tasks can hold rights to the portthat allow them to send (write) messages into the queue.
A task communicates with another task by building a data structurethat contains a set of data elements and then performing a message-sendoperation on a port for which it holds send rights. At some latertime, the task with receive rights to that port will perform a message-receiveoperation.
A message may consist of some or all of the following:
- pure data
- copies of memory ranges
- port rights
- kernel implicit attributes, such as the sender’s security token
The message transfer is an asynchronous operation. The messageis logically copied into the receiving task, possibly with copy-on-writeoptimizations. Multiple threads within the receiving task can beattempting to receive messages from a given port, but only one thread canreceive any given message.
Semaphores
Semaphore IPC objects support wait, post, and post all operations.These are counting semaphores, in that posts are saved (counted)if there are no threads currently waiting in that semaphore’swait queue. A post all operation wakes up all currently waitingthreads.
Notifications
Like semaphores, notification objects also support post andwait operations, but with the addition of a state field. The stateis a fixed-size, fixed-format field that is defined when the notificationobject is created. Each post updates the state field; there is asingle state that is overwritten by each post.
Locks
A lock is an object that provides mutually exclusive accessto a critical section. The primary interfaces to locks are transactionoriented (see IPC Transactions and Event Dispatching). During the transaction,the thread holds the lock. When it returns from the transaction,the lock is released.
Remote Procedure Call (RPC) Objects
The Burden Mac Os Catalina
As the name implies, an RPC object is designed to facilitateand optimize remote procedure calls. The primary interfaces to RPCobjects are transaction oriented (see IPC Transactions and Event Dispatching)
When an RPC object is created, a set of argument block formatsis defined. When an RPC (a send on the object) is made by a client,it causes a message in one of the predefined formats to be createdand queued on the object, then eventually passed to the server (the receiver).When the server returns from the transaction, the reply is returnedto the sender. Mach tries to optimize the transaction by executingthe server using the client’s resources; this is called threadmigration.
Time Management
The Burden Mac Os X
The traditional abstraction of time in Mach is the clock, which provides a setof asynchronous alarm services based on
mach_timespec_t
.There are one or more clock objects, each defining a monotonicallyincreasing time value expressed in nanoseconds. The real-time clockis built in, and is the most important, but there may be other clocksfor other notions of time in the system. Clocks support operationsto get the current time, sleep for a given period, set an alarm(a notification that is sent at a given time), and so forth. The
mach_timespec_t
API is deprecatedin OS X. The newer and preferred API is based on timer objectsthat in turn use AbsoluteTime
asthe basic data type. AbsoluteTime
isa machine-dependent type, typically based on the platform-nativetime base. Routines are provided to convert AbsoluteTime
valuesto and from other data types, such as nanoseconds. Timer objectssupport asynchronous, drift-free notification, cancellation, andpremature alarms. They are more efficient and permit higher resolutionthan clocks.The Burden Mac Os Download
The Burden Mac Os 11
Copyright © 2002, 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-08-08