7.3. Many Threads of a New System Mac OS X was not designed from scratch by one team: It came to be as a conglomeration of vastly different technologies from numerous sources. As a commercial operating system with both legacy and new-adopter user bases to support initially, with a wide spectrum of user needs, Mac OS X includes an unusually large number of mechanisms and interfaces. The user-visible process subsystem is a good example of this phenomenon. Mac OS X has several flavors of user-level threads and processes, depending on the application environment in use. Examples include the following: A Mach thread created within the kernel task, for the kernel's own use A single-threaded BSD process created using the fork() system call A multithreaded BSD process originally created using the fork() system call, followed by the creation of one or more additional threads created using the Pthreads API Multiple Java threads in a Java application created using the java.lang.Thread API A subprocess created using the Cocoa NSTask API A thread created using the Cocoa NSThread API A Carbon Process Manager (CPM) process created by using the LaunchApplication() Carbon API call Preemptively scheduled tasks created in an application using Carbon Multiprocessing Services Cooperatively scheduled threads created in an application using Carbon Thread Manager A thread running in the Classic environment In this section, we will look at specific examples of some of these flavors. At this juncture, the following general points can be noted regarding threads in Mac OS X. The kernel knows about only one type of thread: the Mach thread. Thus, any user-visible entity that runs eventually runs as a Mach thread, although a user library managing that entity may layer multiple such entities atop one Mach thread, running one of them at a time. Note that within the kernel, some threads may be specially designated, such as a Blue thread for Classic, a thread running a virtual machine, a VM-privileged thread, and so on. A first-class process-like entity visible to the user typically has a corresponding BSD process and, in turn, a corresponding Mach task. A first-class thread-like entity visible to the user typically has a corresponding pthread and, in turn, a corresponding Mach thread. We say "typically" and not "always" as it is technically possible to create a Mach task without a BSD process, and a Mach thread without a pthread. There may be other exceptions; for example, the entire Classic environment, along with all its Process Manager processes, corresponds to one BSD process. 7.3.1. Mach Tasks and Threads In previous sections, we saw examples of routines in Mach's task and thread management interfaces. Let us now use these interfaces in some examples to illustrate their working. The Mach task and thread client APIs should not be used by user programs. Surely, unless you are implementing, say, a threading package to replace Pthreads, there is no normally justifiable reason for a program to create Mach tasks or threads directly. 7.3.1.1. Creating a Mach Task In this example, we will create a Mach task using the task_create() call. The new task will have no threads and can optionally inherit memory from its parent task. Recall that Mach tasks do not have parent-child relationships. Specifically, the task structure does not contain information about either the parent task or the child tasks, if any, of that task. Parent-child information is maintained at the BSD level. However, in this case, since we will bypass the BSD layer, there will be no BSD proc structureand correspondingly no process IDfor the new task. Figure 719 shows the program. When run with no arguments, or with a single argument 0, it does not inherit the parent task's inheritable address space. In this case, the task is created with a virtual size of zero, corresponding to an empty address space. Figure 719. Creating a Mach task // task_create.c #include <stdio.h> #include <stdlib.h> #include <mach/mach.h> int main(int argc, char **argv) { kern_return_t kr; pid_t pid; task_t child_task; ledger_t ledgers; ledger_array_t ledger_array; mach_msg_type_number_t ledger_count; boolean_t inherit = TRUE; task_info_data_t info; mach_msg_type_number_t count; struct task_basic_info *task_basic_info; if (argc == 2) inherit = (atoi(argv[1])) ? TRUE : FALSE; // have the kernel use the parent task's ledger ledger_count = 1; ledgers = (ledger_t)0; ledger_array = &ledgers; // create the new task kr = task_create(mach_task_self(), // prototype (parent) task ledger_array, // resource ledgers ledger_count, // number of ledger ports inherit, // inherit memory? &child_task); // port for new task if (kr != KERN_SUCCESS) { mach_error("task_create:", kr); exit(1); } // get information on the new task count = TASK_INFO_MAX; kr = task_info(child_task, TASK_BASIC_INFO, (task_info_t)info, &count); if (kr != KERN_SUCCESS) mach_error("task_info:", kr); else { // there should be no BSD process ID kr = pid_for_task(child_task, &pid); if (kr != KERN_SUCCESS) mach_error("pid_for_task:", kr); task_basic_info = (struct task_basic_info *)info; printf("pid %d, virtual sz %d KB, resident sz %d KB\n", pid, task_basic_info->virtual_size >> 10, task_basic_info->resident_size >> 10); } kr = task_terminate(child_task); if (kr != KERN_SUCCESS) mach_error("task_terminate:", kr); exit(0); } $ gcc -Wall -o task_create task_create.c $ ./task_create 1 pid_for_task: (os/kern) failure pid -1, virtual sz 551524 KB, resident sz 4 KB $ ./task_create 0 pid_for_task: (os/kern) failure pid -1, virtual sz 0 KB, resident sz 0 KB | Resource Ledgers Note that task_create() requires an array of ports representing resource ledgers from which the task is supposed to draw its resources. A resource ledger is a kernel abstraction used for resource accountingit provides a mechanism to control the use of specific resources by one or more tasks. Although the Mac OS X kernel implements the ledger interface, the mechanism is not functional. |
A new Mach task is normally created during a fork() or an execve() that follows a vfork(). A user program should not call task_create(). 7.3.1.2. Creating a Mach Thread in an Existing Task In this example, we will use Mach thread interface functions to create a new thread, set up its machine state, and set it running. Normally, pthread_create(), which is implemented in the system library, calls thread_create() to create a Mach thread. Moreover, pthread_create() initializes or associates various Pthreads-related data items with the thread. Invocations of several functions in the system library result in these data items being referenced. Since we will directly call thread_create(), there will be no corresponding pthread. The program shown in Figure 720 creates a thread that executes a trivial function and exits. Depending on the Mac OS X version, the program may fail when the trivial function calls printf(), since the latter's implementation may reference the calling thread's pthread context, which would be nonexistent in this case. Figure 720. Creating a Mach thread // thread_create.c #include <stdio.h> #include <stdlib.h> #include <mach/mach.h> #include <architecture/ppc/cframe.h> void my_thread_setup(thread_t t); void my_thread_exit(void); void my_thread_routine(int, char *); static uintptr_t threadStack[PAGE_SIZE]; #define EXIT_ON_MACH_ERROR(msg, retval) \ if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); exit((retval)); } int main(int argc, char **argv) { thread_t th; kern_return_t kr; mach_port_name_t mytask, mythread; mytask = mach_task_self(); mythread = mach_thread_self(); // create new thread within our task kr = thread_create(mytask, &th); EXIT_ON_MACH_ERROR("thread_create", kr); // set up the new thread's user mode execution state my_thread_setup(th); // run the new thread kr = thread_resume(th); EXIT_ON_MACH_ERROR("thread_resume", kr); // new thread will call exit // note that we still have an undiscarded reference on mythread thread_suspend(mythread); /* NOTREACHED */ exit(0); } void my_thread_setup(thread_t th) { kern_return_t kr; mach_msg_type_number_t count; ppc_thread_state_t state; void *stack = threadStack; // arguments to my_thread_routine() -- the function run by the new thread int arg1 = 16; char *arg2 = "Hello, Mach!" stack += (PAGE_SIZE - C_ARGSAVE_LEN - C_RED_ZONE); count = PPC_THREAD_STATE_COUNT; kr = thread_get_state(th, // target thread PPC_THREAD_STATE, // flavor of thread state (thread_state_t)&state, &count); EXIT_ON_MACH_ERROR("thread_get_state", kr); //// setup of machine-dependent thread state (PowerPC) state.srr0 = (unsigned int)my_thread_routine; // where to begin execution state.r1 = (uintptr_t)stack; // stack pointer state.r3 = arg1; // first argument to my_thread_routine() state.r4 = (uintptr_t)arg2; // second argument to my_thread_routine() // "return address" for my_thread_routine() state.lr = (unsigned int)my_thread_exit; kr = thread_set_state(th, PPC_THREAD_STATE, (thread_state_t)&state, PPC_THREAD_STATE_COUNT); EXIT_ON_MACH_ERROR("my_thread_setup", kr); } void my_thread_routine(int arg1, char *arg2) { // printf("my_thread_routine(%d, %s)\n", arg1, arg2); // likely to fail puts("my_thread_routine()"); } void my_thread_exit(void) { puts("my_thread_exit(void)"); exit(0); } $ gcc -Wall -o thread_create thread_create.c $ ./thread_create my_thread_routine() my_thread_exit(void) | 7.3.1.3. Displaying Task and Thread Details Let us now write a program to display detailed information about all tasks, and all threads within those tasks, on a system. Our programcalled lstaskswill use a variety of Mach task and thread routines to retrieve relevant information. The program will optionally access a process ID as an argument, in which case it will display information only for tasks and threads associated with that BSD process. Figure 721 shows the implementation and sample usage of lstasks. Figure 721. Retrieving detailed task and thread information from the kernel // lstasks.c #include <getopt.h> #include <sys/sysctl.h> #include <mach/mach.h> #include <Carbon/Carbon.h> #define PROGNAME "lstasks" // pretty-printing macros #define INDENT_L1 " " #define INDENT_L2 " " #define INDENT_L3 " " #define INDENT_L4 " " #define SUMMARY_HEADER \ "task# BSD pid program PSN (high) PSN (low) #threads\n" static const char *task_roles[] = { "RENICED", "UNSPECIFIED", "FOREGROUND_APPLICATION", "BACKGROUND_APPLICATION", "CONTROL_APPLICATION", "GRAPHICS_SERVER", }; #define TASK_ROLES_MAX (sizeof(task_roles)/sizeof(char *)) static const char *thread_policies[] = { "UNKNOWN?", "STANDARD|EXTENDED", "TIME_CONSTRAINT", "PRECEDENCE", }; #define THREAD_POLICIES_MAX (sizeof(thread_policies)/sizeof(char *)) static const char *thread_states[] = { "NONE", "RUNNING", "STOPPED", "WAITING", "UNINTERRUPTIBLE", "HALTED", }; #define THREAD_STATES_MAX (sizeof(thread_states)/sizeof(char *)) #define EXIT_ON_MACH_ERROR(msg, retval) \ if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); exit((retval)); } // get BSD process name from process ID static char * getprocname(pid_t pid) { size_t len = sizeof(struct kinfo_proc); static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 }; static struct kinfo_proc kp; name[3] = pid; kp.kp_proc.p_comm[0] = '\0'; if (sysctl((int *)name, sizeof(name)/sizeof(*name), &kp, &len, NULL, 0)) return "?" if (kp.kp_proc.p_comm[0] == '\0') return "exited?" return kp.kp_proc.p_comm; } void usage() { printf("usage: %s [-s|-v] [-p <pid>]\n", PROGNAME); exit(1); } // used as the printf() while printing only the summary int noprintf(const char *format, ...) { return 0; // nothing } int main(int argc, char **argv) { int i, j, summary = 0, verbose = 0; int (* Printf)(const char *format, ...); pid_t pid; // for Carbon processes OSStatus status; ProcessSerialNumber psn; CFStringRef nameRef; char name[MAXPATHLEN]; kern_return_t kr; mach_port_t myhost; // host port mach_port_t mytask; // our task mach_port_t onetask = 0; // we want only one specific task mach_port_t p_default_set; // processor set name port mach_port_t p_default_set_control; // processor set control port //// for task-related querying // pointer to ool buffer for processor_set_tasks(), and the size of // the data actually returned in the ool buffer task_array_t task_list; mach_msg_type_number_t task_count; // maximum-sized buffer for task_info(), and the size of the data // actually filled in by task_info() task_info_data_t tinfo; mach_msg_type_number_t task_info_count; // flavor-specific pointers to cast the generic tinfo buffer task_basic_info_t basic_info; task_events_info_t events_info; task_thread_times_info_t thread_times_info; task_absolutetime_info_t absolutetime_info; // used for calling task_get_policy() task_category_policy_data_t category_policy; boolean_t get_default; // opaque token that identifies the task as a BSM audit subject audit_token_t audit_token; security_token_t security_token; // kernel's security token is { 0, 1 } //// for thread-related querying // pointer to ool buffer for task_threads(), and the size of the data // actually returned in the ool buffer thread_array_t thread_list; mach_msg_type_number_t thread_count; // maximum-sized buffer for thread_info(), and the size of the data // actually filled in by thread_info() thread_info_data_t thinfo; mach_msg_type_number_t thread_info_count; // flavor-specific pointers to cast the generic thinfo buffer thread_basic_info_t basic_info_th; // used for calling thread_get_policy() thread_extended_policy_data_t extended_policy; thread_time_constraint_policy_data_t time_constraint_policy; thread_precedence_policy_data_t precedence_policy; // to count individual types of process subsystem entities uint32_t stat_task = 0; // Mach tasks uint32_t stat_proc = 0; // BSD processes uint32_t stat_cpm = 0; // Carbon Process Manager processes uint32_t stat_thread = 0; // Mach threads // assume we won't be silent: use the verbose version of printf() by default Printf = printf; myhost = mach_host_self(); mytask = mach_task_self(); while ((i = getopt(argc, argv, "p:sv")) != -1) { switch (i) { case 'p': pid = strtoul(optarg, NULL, 10); kr = task_for_pid(mytask, pid, &onetask); EXIT_ON_MACH_ERROR("task_for_pid", 1); break; case 's': summary = 1; Printf = noprintf; break; case 'v': verbose = 1; break; default: usage(); } } // can't have both if (summary && verbose) usage(); argv += optind; argc -= optind; kr = processor_set_default(myhost, &p_default_set); EXIT_ON_MACH_ERROR("processor_default", 1); // get the privileged port so that we can get all tasks kr = host_processor_set_priv(myhost, p_default_set, &p_default_set_control); EXIT_ON_MACH_ERROR("host_processor_set_priv", 1); // we could check for multiple processor sets, but we know there aren't... kr = processor_set_tasks(p_default_set_control, &task_list, &task_count); EXIT_ON_MACH_ERROR("processor_set_tasks", 1); if (!verbose) Printf(SUMMARY_HEADER); // check out each task for (i = 0; i < task_count; i++) { // ignore our own task if (task_list[i] == mytask) continue; if (onetask && (task_list[i] != onetask)) continue; pid = 0; status = procNotFound; // note that we didn't count this task stat_task++; if (verbose) Printf("Task #%d\n", i); else Printf("%5d", i); // check for BSD process (process 0 not counted as a BSD process) kr = pid_for_task(task_list[i], &pid); if ((kr == KERN_SUCCESS) && (pid > 0)) { stat_proc++; if (verbose) Printf(INDENT_L1 "BSD process id (pid) = %u (%s)\n", pid, getprocname(pid)); else Printf(" %6u %-16s", pid, getprocname(pid)); } else // no BSD process if (verbose) Printf(INDENT_L1 "BSD process id (pid) = " "/* not a BSD process */\n"); else Printf(" %6s %-16s", "-", "-"); // check whether there is a process serial number if (pid > 0) status = GetProcessForPID(pid, &psn); if (status == noErr) { stat_cpm++; if (verbose) { status = CopyProcessName(&psn, &nameRef); CFStringGetCString(nameRef, name, MAXPATHLEN, kCFStringEncodingASCII); Printf(INDENT_L1 "Carbon process name = %s\n", name); CFRelease(nameRef); } else Printf(" %-12d%-12d", psn.highLongOfPSN, psn.lowLongOfPSN); } else // no PSN if (verbose) Printf(INDENT_L1 "Carbon process name = " "/* not a Carbon process */\n"); else Printf(" %-12s%-12s", "-", "-"); if (!verbose) goto do_threads; // basic task information task_info_count = TASK_INFO_MAX; kr = task_info(task_list[i], TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); if (kr != KERN_SUCCESS) { mach_error("task_info:", kr); fprintf(stderr, "*** TASK_BASIC_INFO failed (task=%x)\n", task_list[i]); // skip this task continue; } basic_info = (task_basic_info_t)tinfo; Printf(INDENT_L2 "virtual size = %u KB\n", basic_info->virtual_size >> 10); Printf(INDENT_L2 "resident size = %u KB\n", basic_info->resident_size >> 10); if ((basic_info->policy < 0) && (basic_info->policy > THREAD_POLICIES_MAX)) basic_info->policy = 0; Printf(INDENT_L2 "default policy = %u (%s)\n", basic_info->policy, thread_policies[basic_info->policy]); Printf(INDENT_L1 "Thread run times\n"); Printf(INDENT_L2 "user (terminated) = %u s %u us\n", basic_info->user_time.seconds, basic_info->user_time.microseconds); Printf(INDENT_L2 "system (terminated) = %u s %u us\n", basic_info->system_time.seconds, basic_info->system_time.microseconds); // times for live threads (unreliable -- we are not suspending) task_info_count = TASK_INFO_MAX; kr = task_info(task_list[i], TASK_THREAD_TIMES_INFO, (task_info_t)tinfo, &task_info_count); if (kr == KERN_SUCCESS) { thread_times_info = (task_thread_times_info_t)tinfo; Printf(INDENT_L2 "user (live) = %u s %u us\n", thread_times_info->user_time.seconds, thread_times_info->user_time.microseconds); Printf(INDENT_L2 "system (live) = %u s %u us\n", thread_times_info->system_time.seconds, thread_times_info->system_time.microseconds); } // absolute times for live threads, and overall absolute time task_info_count = TASK_INFO_MAX; kr = task_info(task_list[i], TASK_ABSOLUTETIME_INFO, (task_info_t)tinfo, &task_info_count); if (kr == KERN_SUCCESS) { Printf(INDENT_L1 "Thread times (absolute)\n"); absolutetime_info = (task_absolutetime_info_t)tinfo; Printf(INDENT_L2 "user (total) = %lld\n", absolutetime_info->total_user); Printf(INDENT_L2 "system (total) = %lld\n", absolutetime_info->total_system); Printf(INDENT_L2 "user (live) = %lld\n", absolutetime_info->threads_user); Printf(INDENT_L2 "system (live) = %lld\n", absolutetime_info->threads_system); } // events task_info_count = TASK_INFO_MAX; kr = task_info(task_list[i], TASK_EVENTS_INFO, (task_info_t)tinfo, &task_info_count); if (kr == KERN_SUCCESS) { events_info = (task_events_info_t)tinfo; Printf(INDENT_L2 "page faults = %u\n", events_info->faults); Printf(INDENT_L2 "actual pageins = %u\n", events_info->pageins); Printf(INDENT_L2 "copy-on-write faults = %u\n", events_info->cow_faults); Printf(INDENT_L2 "messages sent = %u\n", events_info->messages_sent); Printf(INDENT_L2 "messages received = %u\n", events_info->messages_received); Printf(INDENT_L2 "Mach system calls = %u\n", events_info->syscalls_mach); Printf(INDENT_L2 "Unix system calls = %u\n", events_info->syscalls_unix); Printf(INDENT_L2 "context switches = %u\n", events_info->csw); } // task policy information task_info_count = TASK_CATEGORY_POLICY_COUNT; get_default = FALSE; kr = task_policy_get(task_list[i], TASK_CATEGORY_POLICY, (task_policy_t)&category_policy, &task_info_count, &get_default); if (kr == KERN_SUCCESS) { if (get_default == FALSE) { if ((category_policy.role >= -1) && (category_policy.role < (TASK_ROLES_MAX - 1))) Printf(INDENT_L2 "role = %s\n", task_roles[category_policy.role + 1]); } else // no current settings -- other parameters take precedence Printf(INDENT_L2 "role = NONE\n"); } // audit token task_info_count = TASK_AUDIT_TOKEN_COUNT; kr = task_info(task_list[i], TASK_AUDIT_TOKEN, (task_info_t)&audit_token, &task_info_count); if (kr == KERN_SUCCESS) { int n; Printf(INDENT_L2 "audit token = "); for (n = 0; n < sizeof(audit_token)/sizeof(uint32_t); n++) Printf("%x ", audit_token.val[n]); Printf("\n"); } // security token task_info_count = TASK_SECURITY_TOKEN_COUNT; kr = task_info(task_list[i], TASK_SECURITY_TOKEN, (task_info_t)&security_token, &task_info_count); if (kr == KERN_SUCCESS) { int n; Printf(INDENT_L2 "security token = "); for (n = 0; n < sizeof(security_token)/sizeof(uint32_t); n++) Printf("%x ", security_token.val[n]); Printf("\n"); } do_threads: // get threads in the task kr = task_threads(task_list[i], &thread_list, &thread_count); if (kr != KERN_SUCCESS) { mach_error("task_threads:", kr); fprintf(stderr, "task_threads() failed (task=%x)\n", task_list[i]); continue; } if (thread_count > 0) stat_thread += thread_count; if (!verbose) { Printf(" %8d\n", thread_count); continue; } Printf(INDENT_L1 "Threads in this task = %u\n", thread_count); // check out threads for (j = 0; j < thread_count; j++) { thread_info_count = THREAD_INFO_MAX; kr = thread_info(thread_list[j], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count); if (kr != KERN_SUCCESS) { mach_error("task_info:", kr); fprintf(stderr, "*** thread_info() failed (task=%x thread=%x)\n", task_list[i], thread_list[j]); continue; } basic_info_th = (thread_basic_info_t)thinfo; Printf(INDENT_L2 "thread %u/%u (%p) in task %u (%p)\n", j, thread_count - 1, thread_list[j], i, task_list[i]); Printf(INDENT_L3 "user run time = %u s %u us\n", basic_info_th->user_time.seconds, basic_info_th->user_time.microseconds); Printf(INDENT_L3 "system run time = %u s %u us\n", basic_info_th->system_time.seconds, basic_info_th->system_time.microseconds); Printf(INDENT_L3 "scaled cpu usage percentage = %u\n", basic_info_th->cpu_usage); switch (basic_info_th->policy) { case THREAD_EXTENDED_POLICY: get_default = FALSE; thread_info_count = THREAD_EXTENDED_POLICY_COUNT; kr = thread_policy_get(thread_list[j], THREAD_EXTENDED_POLICY, (thread_policy_t)&extended_policy, &thread_info_count, &get_default); if (kr != KERN_SUCCESS) break; Printf(INDENT_L3 "scheduling policy = %s\n", (extended_policy.timeshare == TRUE) ? \ "STANDARD" : "EXTENDED"); break; case THREAD_TIME_CONSTRAINT_POLICY: get_default = FALSE; thread_info_count = THREAD_TIME_CONSTRAINT_POLICY_COUNT; kr = thread_policy_get(thread_list[j], THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t)&time_constraint_policy, &thread_info_count, &get_default); if (kr != KERN_SUCCESS) break; Printf(INDENT_L3 "scheduling policy = " \ "TIME_CONSTRAINT\n"); Printf(INDENT_L4 "period = %-4u\n", time_constraint_policy.period); Printf(INDENT_L4 "computation = %-4u\n", time_constraint_policy.computation); Printf(INDENT_L4 "constraint = %-4u\n", time_constraint_policy.constraint); Printf(INDENT_L4 "preemptible = %s\n", (time_constraint_policy.preemptible == TRUE) ? \ "TRUE" : "FALSE"); break; case THREAD_PRECEDENCE_POLICY: get_default = FALSE; thread_info_count = THREAD_PRECEDENCE_POLICY; kr = thread_policy_get(thread_list[j], THREAD_PRECEDENCE_POLICY, (thread_policy_t)&precedence_policy, &thread_info_count, &get_default); if (kr != KERN_SUCCESS) break; Printf(INDENT_L3 "scheduling policy = PRECEDENCE\n"); Printf(INDENT_L4 "importance = %-4u\n", precedence_policy.importance); break; default: Printf(INDENT_L3 "scheduling policy = UNKNOWN?\n"); break; } Printf(INDENT_L3 "run state = %-4u (%s)\n", basic_info_th->run_state, (basic_info_th->run_state >= THREAD_STATES_MAX) ? \ "?" : thread_states[basic_info_th->run_state]); Printf(INDENT_L3 "flags = %-4x%s", basic_info_th->flags, (basic_info_th->flags & TH_FLAGS_IDLE) ? \ " (IDLE)\n" : "\n"); Printf(INDENT_L3 "suspend count = %u\n", basic_info_th->suspend_count); Printf(INDENT_L3 "sleeping for time = %u s\n", basic_info_th->sleep_time); } // for each thread vm_deallocate(mytask, (vm_address_t)thread_list, thread_count * sizeof(thread_act_t)); } // for each task Printf("\n"); fprintf(stdout, "%4d Mach tasks\n%4d Mach threads\n" "%4d BSD processes\n%4d CPM processes\n", stat_task, stat_thread, stat_proc, stat_cpm); vm_deallocate(mytask, (vm_address_t)task_list, task_count * sizeof(task_t)); exit(0); } $ gcc -Wall -o lstasks lstasks.c -framework Carbon $ sudo ./lstasks task# BSD pid program PSN (high) PSN (low) #threads 0 - - - - 49 1 1 launchd - - 3 2 26 dynamic_pager - - 1 3 30 kextd - - 2 ... 93 12149 vim - - 1 94 Mach tasks 336 Mach threads 93 BSD processes 31 CPM processes $ sudo ./lstasks -v -p $$ Task #49 BSD process id (pid) = 251 (zsh) Carbon process name = /* not a Carbon process */ virtual size = 564368 KB resident size = 13272 KB default policy = 1 (STANDARD|EXTENDED) Thread run times user (terminated) = 0 s 0 us system (terminated) = 0 s 0 us user (live) = 19 s 501618 us system (live) = 37 s 98274 us Thread times (absolute) user (total) = 649992326 system (total) = 1236491913 user (live) = 649992326 system (live) = 1236491913 page faults = 3303963 actual pageins = 9 copy-on-write faults = 41086 messages sent = 257 messages received = 251 Mach system calls = 279 Unix system calls = 107944 context switches = 67653 role = UNSPECIFIED audit token = 0 1f5 1f5 1f5 1f5 fb 0 0 security token = 1f5 1f5 Threads in this task = 1 thread 0/0 (0x8003) in task 49 (0x113) user run time = 19 s 501618 us system run time = 37 s 98274 us scaled cpu usage percentage = 34 scheduling policy = STANDARD run state = 3 (WAITING) flags = 1 suspend count = 0 sleeping for time = 0 s ... | As shown in Figure 721, lstasks also displays the number of Mach and Unix (BSD) system calls made by the process. Let us write a test program that makes a specific number of Mach and Unix system calls and use lstasks to verify the numbers. Figure 722 shows the program and its usage. Note that the usage shown includes outputs of two command shells intermingled. Figure 722. Counting the number of system calls made by a process // syscalls_test.c #include <stdio.h> #include <fcntl.h>> #include <unistd.h> #include <mach/mach.h> int main() { int i, fd; mach_port_t p; kern_return_t kr; setbuf(stdout, NULL); printf("My pid is %d\n", getpid()); printf("Note the number of Mach and Unix system calls, and press <enter>"); (void)getchar(); // At this point, we will have some base numbers of Mach and Unix // system calls made so far, say, M and U, respectively for (i = 0; i < 100; i++) { // 100 iterations // +1 Unix system call per iteration fd = open("/dev/null", O_RDONLY); // +1 Unix system call per iteration close(fd); // +1 Mach system call per iteration kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p); // +1 Mach system call per iteration kr = mach_port_deallocate(mach_task_self(), p); } // +1 Unix system call printf("Note the number of Mach and Unix system calls again...\n" "Now sleeping for 60 seconds..."); // sleep(3) is implemented using nanosleep, which will call // clock_get_time() and clock_sleep_trap() -- this is +2 Mach system calls (int)sleep(60); // Mach system calls = M + 2 * 100 + 2 (that is, 202 more calls) // Unix system calls = U + 2 * 100 + 1 (that is, 201 more calls) return 0; } $ gcc -Wall -o syscalls_test syscalls_test.c $ ./syscalls_test My pid is 12344 Note the number of Mach and Unix system calls, and press <enter> $ sudo ./lstasks -v -p 12344 ... Mach system calls = 71 Unix system calls = 47 ... <enter> Note the number of Mach and Unix system calls again... Now sleeping for 60 seconds... $ sudo ./lstasks -v -p 12344 ... Mach system calls = 273 Unix system calls = 248 ... | 7.3.2. BSD Processes A BSD process is the representation of an application in execution on Mac OS Xall Mac OS X application environments use BSD processes. Unless otherwise stated, we will use the term process to mean a BSD process. As on a typical Unix system, the only way to create a process is through the fork() system call (or through the vfork() variant). A Mach task is a byproduct of the fork() system call on Mac OS X. Whereas the task encapsulates resources managed by Mach, such as address space and IPC space, a BSD process manages Unix-specific resources and abstractions, such as file descriptors, credentials, and signals. Figure 723 shows an excerpt from the proc structure. Figure 723. The BSD proc structure // bsd/sys/proc_internal.h struct proc { LIST_ENTRY(proc) p_list; // list of all processes struct pcred *p_cred; // process owner's credentials struct filedesc *p_fd; // open files structure struct pstats *p_stats; // accounting/statistics struct plimit *p_limits; // process limits struct sigacts *p_sigacts; // signal actions, state ... pid_t p_pid; // process identifier LIST_ENTRY(proc) p_pglist; // list of processes in process group struct proc *p_pptr; // parent process LIST_ENTRY(proc) p_sibling; // list of siblings LIST_HEAD(, proc) p_children; // list of children ... void *p_wchan; // sleep address ... struct vnode *p_textvp; // vnode of the executable ... // various signal-management fields ... void *task; // corresponding task ... }; | As we saw earlier, each Mach thread in a BSD process has an associated utHRead structure to maintain thread-specific Unix information. For example, the uthread structure is used for holding system call parameters and results, miscellaneous state for system calls that use continuations, per-thread signal information, and per-thread credentials. In this sense, the uthread structure is to the thread structure what the proc structure is to the task structure. Figure 724 shows an excerpt from the uthread structure. Figure 724. The BSD utHRead structure // bsd/sys/user.h struct uthread { int *uu_ar0; // address of user's saved R0 int uu_arg[8]; // arguments to the current system call int *uu_ap; // pointer to the system call argument list int uu_rval[2]; // system call return values // thread exception handling int uu_code; // exception code char uu_cursig; // p_cursig for exception ... // space for continuations: // - saved state for select() // - saved state for nfsd // - saved state for kevent_scan() // - saved state for kevent() int (* uu_continuation)(int); ... struct proc *uu_proc; // our proc structure ... // various pieces of signal information sigset_t uu_siglist; // signals pending for the thread sigset_t uu_sigmask; // signal mask for the thread ... thread_act_t uu_act; // our activation ... // list of uthreads in the process TAILQ_ENTRY(uthread) uu_list; ... }; | 7.3.2.1. The fork() System Call: User-Space Implementation Let us now look at the implementation of the fork() system call in Mac OS X. Figure 725 shows the user-space processing of fork()that is, within the system library. Figure 725. User-space processing of the fork() system call When a user program calls fork(), the system library stub invokes several internal functions in preparation for the system call. The dynamic version (which is the default) of the library also invokes fork()-time hooks implemented by the dynamic link editor. The _cthread_fork_prepare() internal function runs any pre-fork() handlers that may have been registered through pthread_atfork(3). Figure 726 shows an example depicting the use of pthread_atfork(3). _cthread_fork_prepare() also acquires library-level critical section locks and prepares the malloc module for a fork() by ensuring that no thread is in a malloc critical section. Figure 726. Registering handlers to be run before and after a fork() system call invocation // pthread_atfork.c #include <stdio.h> #include <unistd.h> #include <pthread.h> // handler to be called before fork() void prepare(void) { printf("prepare\n"); } // handler to be called after fork() in the parent void parent(void) { printf("parent\n"); } // handler to be called after fork() in the child void child(void) { printf("child\n"); } int main(void) { (void)pthread_atfork(prepare, parent, child); (void)fork(); _exit(0); } $ gcc -Wall -o pthread_atfork pthread_atfork.c $ ./pthread_atfork prepare child parent | Thereafter, the stub looks up the address of a pre-fork() prepare functionif anyimplemented by dyld. If the function is found, the stub runs it. Next, the stub invokes the fork() system call. The stub handles three types of returns from the system call: a failed return to the parent, a successful return to the parent, and a successful return to the child. If the system call returned an error, the stub looks up and calls a post-fork() parent functionif anyimplemented by dyld. Then it calls cerror(), a library-internal function that sets the value of the per-thread errno variable. In the case of detached pthreads,[14] the thread-local errno variable resides as a field of the pthread data structure (struct _pthread).[15] The stub then arranges for a -1 to be returned to the caller. After this point, the processing of an erroneous return is similar to a successful return to the parent. In the latter case, on return from the system call, the stub would have arranged for the child's process ID to be returned to the caller. Both types of return to the parent finally call _cthread_fork_parent(), which releases locks taken before the system call and runs any post-fork() handlers that may be registered for running in the parent. [14] If a pthread is not detached, the global errno variable is used instead. [15] This is the per-pthread structure internally maintained by the Pthreads library. The stub performs substantially more work while returning to the child. It first calls a post-fork() child functionif anyimplemented by dyld and then calls _cthread_fork_child(), which calls several functions to perform operations such as the following. Invoke the fastpath system call to set the "self" value for the pthread. Set various fields of the pthread structurefor example, set the kernel_thread and reply_port fields to the values returned by mach_thread_self() and mach_reply_port(), respectively. Release locks taken before the system call. Cache the values of system constants (such as the page size), and the thread's key Mach ports (such as those for the host and the containing task). Initialize special ports for the thread (such as the bootstrap port and the system clock's service port). Reserve page 0 by mapping a page of zero-filled memory at address 0x0, with a protection value of VM_PROT_NONE, which disallows all access to the memory region. Insert the current pthreadthe only one in the process at this pointat the head of the library-maintained list of pthreads for the process, while setting the thread count to 1. Run any post-fork() handlers that may be registered for running in the child. 7.3.2.2. The fork() System Call: Kernel Implementation Let us now look at how the fork() system call is processed in the kernel. Figure 727 provides an overview of the kernel functions involved in fork()'s handling, starting from fork() [bsd/kern/kern_fork.c], the system call handler. Figure 727. Kernel-space processing of the fork() system call fork1() [bsd/kern/kern_fork.c] calls cloneproc() [bsd/kern/kern_fork.c], which creates a new process from the given process. cloneproc() first calls forkproc() [bsd/kern/kern_fork.c], which allocates a new proc structure and several of its constituent structures. forkproc() then finds a free process ID, wrapping around if the next available process ID is higher than PID_MAX (defined to be 30,000 in bsd/sys/proc_internal.h). When process IDs wrap around, the search for the next available ID starts at 100and not 0since low-numbered process IDs are likely to be in use by forever-running daemons. forkproc() initializes various fields of the proc structure and implements several Unix aspects of fork(), such as inheriting the parent's credentials, open file descriptors, and shared memory descriptors. On return from forkproc(), which returns the child proc structure, cloneproc() calls procdup() [bsd/kern/kern_fork.c], passing it the proc structures of the parent and the child. procdup() calls task_create_internal() [osfmk/kern/task.c] to create a new Mach task. The new task structure's bsd_info field is set to point to the child proc structure. procdup() then calls thread_create() [osfmk/kern/thread.c] to create a Mach thread within the new task. thread_create() calls thread_create_internal() [osfmk/kern/thread.c], which allocates a tHRead structure and a uthread structure. Moreover, tHRead_create_internal() initializes various aspects of the new thread, including its machine-dependent state. thread_create() passes a continuation function to tHRead_create_internal(). The function is set as the thread's continuationthat is, the location where the thread will continue when dispatched. The continuation function arranges for the new thread to return to user mode as if the thread were trapping after being interrupted. The newly created thread is returned by procdup() to cloneproc(). On return from procdup(), cloneproc() places the child's proc structure on various liststhe list of its parent's children, the list of all processes, and the process ID hash table. It marks the child as runnable and returns the child thread to fork1(), which now calls tHRead_dup() [osfmk/kern/thread_act.c]. tHRead_dup() calls machine_thread_dup() [osfmk/ppc/status.c] to duplicate the context of the parent (current) thread into the child thread. fork1() calls tHRead_resume() on the child thread and finally returns to the parent. The kernel returns 1 and 0 to the child and parent, respectively, in GPR4. Moreover, the child's process ID is returned in GPR3 in both cases. The system library returns a 0 to the child process and the child's process ID to the parent process. Figure 728 shows a program that directly invokes the fork() system call, after which it writes the values of GPR3 and GPR4 to the standard output. Since we bypass the system library, none of the pre- and post-fork() work normally performed by the system library will be performed in this case. Consequently, we will be unable to use most of the system library functions in the child process. For example, even printf(3) will not work. Therefore, we will use write(2) to display raw, unformatted values of GPR3 and GPR4 on the standard output, which we can pipe to a program such as hexdump. Figure 728. Verifying the values returned by the kernel in the case of a raw fork() system call // fork_demo.c #include <unistd.h> #include <sys/syscall.h> int main(void) { long r3_r4[] = { -1, -1 }; int syscallnum = SYS_fork; __asm__ volatile( "lwz r0,%2 ; load GPR0 with SYS_fork\n" "sc ; invoke fork(2)\n" "nop ; this will be skipped in the case of no error\n" "mr %0,r3 ; save GPR3 to r3_r4[0]\n" "mr %1,r4 ; save GPR4 to r3_r4[1]\n" : "=r"(r3_r4[0]), "=r"(r3_r4[1]) : "g" (syscallnum) ); // write GPR3 and GPR4 write(1, r3_r4, sizeof(r3_r4)/sizeof(char)); // sleep for 30 seconds so we can check process IDs using 'ps' sleep(30); return 0; } $ gcc -Wall -o fork_demo fork_demo.c $ ./fork_demo | hexdump -d 0000000 00000 14141 00000 00000 00000 14141 00000 00001 ... $ ps ... 14139 p9 S+ 0:00.01 ./fork_demo 14140 p9 S+ 0:00.00 hexdump -d 14141 p9 S+ 0:00.00 ./fork_demo | In Figure 728, the first two 32-bit words in hexdump's output correspond to the GPR3 and GPR4 values returned to the parent, whereas the last two words correspond to the child. Note that GPR3 contains the child's process ID in both cases. fork() and Mach Ports: Caveat Only certain Mach ports are inherited by the child task from the parent task during a fork() call. These include registered ports, exception ports, the host port, and the bootstrap port. Other than the inherited ports, any other Mach ports are invalidated in the child. Since the child's address space is inherited from the parent, the child will have bogus port names for the invalidated ports, but no rights to such ports. Although the system library reinitializes several key ports in the child process after fork(), not every library or framework performs such cleaning. If you run into the latter situation, one solution is to execute the same binary again through execve(). The program may have to explicitly accommodate this solution. |
Every BSD process begins life with one Mach task, one Mach thread, a BSD proc structure associated with the task structure, a uthread structure associated with the thread structure, and a pthread implemented within the user-space system library. As we saw in Chapter 6, the pid_for_task() Mach routine can be used to retrieve the BSD process ID of a Mach taskprovided it has one. Conversely, task_for_pid() retrieves the task port for the specified BSD process ID. ... kern_return_t kr; pid_t pid; mach_port_t task; ... // get BSD process ID for the task with the specified port kr = pid_for_task(task, &pid); ... // get task port for task on the same host as the given task, // and one with the given BSD process ID kr = task_for_pid(mach_task_self(), pid, &task); 7.3.2.3. The vfork() System Call The vfork() system call is a variant of fork() that can be used to create a new process without fully copying the address space of the parent process. It is an optimized version of fork() intended for the case when the new process is being created for calling execve(). The following aspects describe the implementation and usage of vfork() on Mac OS X. The parent process is blocked while the child process executes using the parent's resources. The expectedand correctuse of vfork() requires the child process to either call execve() or to exit. Thereafter, the parent resumes execution. Calling vfork() creates a new BSD proc structure for the child process, but no task or thread is created. Therefore, in this case, two BSD proc structures refer to the same task structure. The task and the initial thread for the child process are eventually created during the processing of the execve() system call. During its execution, the child must be careful not to cause undesirable alterations to the parent's stack or other memory. Figure 729 shows the processing of vfork() in the kernel. Figure 729. Kernel-space processing of the vfork() system call Let us look at an example to show that the child process borrows the parent's resources temporarily. The program in Figure 730 prints the task and thread portsas returned by mach_task_self() and mach_thread_self(), respectivelyin the parent and the child. When run with no arguments, the program calls vfork(). Otherwise, it calls fork(). Moreover, the child modifies the value of an automatic variable declared in the parent. In the case of vfork(), the change is reflected in the parent after the parent resumes. Figure 730. Verifying that the child borrows the parent's resources during a vfork() system call // vfork_demo.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> #include <mach/mach.h> int main(int argc, char **argv) { int ret, i = 0; printf("parent: task = %x, thread = %x\n", mach_task_self(), pthread_mach_thread_np(pthread_self())); // vfork() if no extra command-line arguments if (argc == 1) ret = vfork(); else ret = fork(); if (ret < 0) exit(ret); if (ret == 0) { // child i = 1; printf("child: task = %x, thread = %x\n", mach_task_self(), pthread_mach_thread_np(pthread_self())); _exit(0); } else printf("parent, i = %d\n", i); return 0; } $ gcc -Wall -o vfork_demo vfork_demo.c $ ./vfork_demo parent: task = 807, thread = d03 child: task = 807, thread = d03 parent, i = 1 $ ./vfork_demo use_vfork parent: task = 807, thread = d03 parent, i = 0 child: task = 103, thread = 203 | vfork() cannot be called recursively. After a vfork(), calling vfork() again from the child processwhile the parent is blockedwill return an EINVAL error to the caller. 7.3.3. POSIX Threads (Pthreads) Subsequent threads can be created within a BSD process through the POSIX Threads (Pthreads) API, which is also the fundamental threading mechanism in the Mac OS X user space. Thread packages in all common application environments are built atop Pthreads. 7.3.3.1. The Pthreads API The Pthreads library, which exists as a module within the system library, implements POSIX threads atop Mach threads by using Mach thread API functions to create, terminate, and manipulate the state of kernel threads. In particular, the library manages the program counters and stacks of threads from user space. Specific Mach routines used by the Pthreads library include the following: Besides standard-conformant functions, the Mac OS X Pthreads implementation also provides a set of nonportable functions that can be used by user programs at the cost of portability. Such functions have the suffix _np in their names to indicate their nonportability. The following are examples of nonportable functions. pthread_cond_signal_thread_np() signals a condition variable, waking a specific thread. pthread_cond_timedwait_relative_np() waits on a condition variable, but in a nonstandard manner, allowing a relative time to be specified for sleeping. pthread_create_suspended_np() creates a pthread with the underlying Mach thread suspended. pthread_get_stackaddr_np() retrieves a given pthread's stack address from the corresponding pthread structure. pthread_get_stacksize_np() retrieves a given pthread's stack size from the corresponding pthread structure. pthread_is_threaded_np() returns a 1 if the current process has (or had) at least one thread in addition to the default, main thread. This function is susceptible to race conditions. pthread_mach_thread_np() returns the Mach thread corresponding to the given pthread. It is equivalent to calling mach_thread_self(), except that it returns an existing reference to the thread's kernel port. In contrast, mach_thread_self() returns a new reference that should be released by calling mach_port_deallocate() when not needed. pthread_main_np() returns a nonzero value if the current thread is the main thread. pthread_yield_np() invokes the swtch_pri() Mach trap to attempt a context switch, yielding to another runnable thread, if any. Note that swtch_pri() also sets the current thread's scheduling priority. 7.3.3.2. Implementation of Pthread Creation Figure 731 shows how a new pthread is created in the system library. Note that the creation of the underlying Mach thread by the Pthreads library is similar to our example from Figure 720. Figure 731. Pthread creation in the system library A BSD process cannot be created on Mac OS X without an associated Mach task. Similarly, a pthread cannot be created without an associated Mach thread. Hereafter, we will not point out the implicit existence of a Mach task for every BSD process or of a Mach thread for every pthread. 7.3.4. Java Threads Java's threading class (java.lang.Thread) is used to create a new thread of execution in a Java application. You can subclass java.lang.Thread and override the run() method, in which case, when the thread is started, it will execute the body of the run() method. The run() method is required by the Runnable interface (java.lang.Runnable), which java.lang.Thread implements. Alternatively, you can construct a new thread by passing it an instance of any class that implements the Runnable interface. This way, any class can provide the body of the thread without being a subclass of java.lang.Thread. As you start a Java application on Mac OS X, one BSD process is created for the instance of the Java virtual machine (JVM). Since the JVM itself has a multithreaded implementation, this process contains several pthreads to begin with. Thereafter, each java.lang.Thread thread maps to one pthread and therefore one Mach thread. Figure 732 shows an example of creating and running Java threads on Mac OS X. You can use the lstasks program from Figure 721 to list the Mach threads in the JVM processsay, by adding one or more calls to Thread.sleep() in the Java program to prevent it from exiting too quickly to be examined. Figure 732. Creating and running Java threads // MyThread.java import java.lang.ThreadGroup; import java.lang.Thread; class MyThread extends Thread { MyThread(ThreadGroup group, String name) { super(group, name); } public void run() { for (int i = 0; i < 128; i++) System.out.print(this.getName()); } } class DemoApp { public static void main(String[] args) { ThreadGroup allThreads = new ThreadGroup("Threads"); MyThread t1 = new MyThread(allThreads, "1"); MyThread t2 = new MyThread(allThreads, "2"); MyThread t3 = new MyThread(allThreads, "3"); allThreads.list(); t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority((Thread.MAX_PRIORITY + Thread.MIN_PRIORITY) / 2); t3.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start(); t3.start(); } } $ javac MyThread.java $ CLASSPATH=. java DemoApp java.lang.ThreadGroup[name=Threads,maxpri=10] Thread[1,5,Threads] Thread[2,5,Threads] Thread[3,5,Threads] 32333333333333333333333333333333333333333333333333333333333333333333333333333333333 33333333333333333333333333333333333333333333332222222222222222222222222222222222222 22222222222222222222222222222222222222222222222222222222222222222222222222222222222 22222221111111111111111111111111111111111111111111111111111111111111111 | 7.3.5. The NSTask Cocoa Class The NSTask class in Cocoa allows the creation and launching of a task running a given executable. Using NSTask, the launched program can be treated as a subprocess from the standpoint of monitoring it. Some aspects of the program's execution can also be controlled through NSTask. Note that NSTask is built atop the Unix fork() and execve() system calls and therefore offers similar functionality. Figure 733 shows an example of using NSTask. Figure 733. Using the NSTask Cocoa class // NSTask.m #import <Foundation/Foundation.h> #define TASK_PATH "/bin/sleep" #define TASK_ARGS "5" int main() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSTask *newTask; int status; // allocate and initialize an NSTask object newTask = [[NSTask alloc] init]; // set the executable for the program to be run [newTask setLaunchPath:@TASK_PATH]; // set the command arguments that will be used to launch the program [newTask setArguments:[NSArray arrayWithObject:@TASK_ARGS]]; // launch the program -- a new process will be created [newTask launch]; NSLog(@"waiting for new task to exit\n"); [newTask waitUntilExit]; // fetch the value returned by the exiting program status = [newTask terminationStatus]; NSLog(@"new task exited with status %d\n", status); [pool release]; exit(0); } $ gcc -Wall -o nstask NSTask.m -framework Foundation $ ./nstask 2005-08-12 13:42:44.427 nstask[1227] waiting for new task to exit 2005-08-12 13:42:49.472 nstask[1227] new task exited with status 0 | 7.3.6. The NSThread Cocoa Class The NSThread class allows for creation of multiple threads in Cocoa applications. It is particularly convenient to use NSThread for running an Objective-C method in its own thread. Each instance of the NSThread class controls one thread of execution, which maps to one pthread. Figure 734 shows an example of using NSThread. Figure 734. Using the NSThread Cocoa class // NSThread.m #import <Foundation/Foundation.h> @interface NSThreadController : NSObject { unsigned long long sum1; unsigned long long sum2; } - (void)thread1:(id)arg; - (void)thread2:(id)arg; - (unsigned long long)get_sum1; - (unsigned long long)get_sum2; @end @implementation NSThreadController - (unsigned long long)get_sum1 { return sum1; } - (unsigned long long)get_sum2 { return sum2; } - (void)thread1:(id)arg { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [NSThread setThreadPriority:0.0]; sum1 = 0; printf("thread1: running\n"); for (;;) sum1++; [pool release]; } - (void)thread2:(id)arg { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [NSThread setThreadPriority:1.0]; sum2 = 0; printf("thread2: running\n"); for (;;) sum2++; [pool release]; } @end int main() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSTimeInterval secs = 5; NSDate *sleepForDate = [NSDate dateWithTimeIntervalSinceNow:secs]; NSThreadController *T = [[NSThreadController alloc] init]; [NSThread detachNewThreadSelector:@selector(thread1:) toTarget:T withObject:nil]; [NSThread detachNewThreadSelector:@selector(thread2:) toTarget:T withObject:nil]; printf("main: sleeping for %f seconds\n", secs); [NSThread sleepUntilDate:sleepForDate]; printf("sum1 = %lld\n", [T get_sum1]); printf("sum2 = %lld\n", [T get_sum2]); [pool release]; exit(0); } $ gcc -Wall -o nsthread NSThread.m -framework Foundation $ ./nsthread main: sleeping for 5.000000 seconds thread2: running thread1: running sum1 = 49635095 sum2 = 233587520 | 7.3.7. The Carbon Process Manager The Process Manager provided a cooperative multitasking environment on several versions of Mac OS prior to Mac OS X. It is supported on Mac OS X as the Carbon Process Manager (CPM), but certain functionality and aspects that are not applicable in the Mac OS X environment are either unavailable or have been modified for accommodation in Mac OS X's different architecture. Each CPM process maps to a BSD process, but not vice versa. Only those processes that are launched through the CPM are managed by Carbon. For each process that it manages, the CPM maintains certain state, including a process serial number (PSN) that is different from the BSD process ID. The PSN consists of a high part and a low part, both of which are unsigned long quantities. struct ProcessSerialNumber { unsigned long highLongOfPSN; unsigned long lowLongOfPSN; }; typedef struct ProcessSerialNumber ProcessSerialNumber; typedef ProcessSerialNumber *ProcessSerialNumberPtr; You can start a CPM process using the Carbon API's LaunchApplication() function, which launches an application from the specified file and returns the PSN on a successful launch. As we saw in the implementation of our lstasks program, given a CPM process, the corresponding PSN and Carbon-specific process name can be retrieved using GetProcessForPID() and CopyProcessName(), respectively. CPM processes can be identified in the process listing generated by the ps command, as they are shown with an argument of the form -psn_X_Y, where X and Y are the high and low parts, respectively, of the PSN. Figure 735 shows an example of launching a CPM process. Figure 735. Launching an application through the Carbon Process Manager // CarbonProcessManager.c #include <Carbon/Carbon.h> #define PROGNAME "cpmtest" int main(int argc, char **argv) { OSErr err; Str255 path; FSSpec spec; LaunchParamBlockRec launchParams; if (argc != 2) { printf("usage: %s <full application path>\n", PROGNAME); exit(1); } c2pstrcpy(path, argv[1]); err = FSMakeFSSpec(0, // use the default volume 0, // parent directory -- determine from filename path, &spec); if (err != noErr) { printf("failed to make FS spec for application (error %d).\n", err); exit(1); } // the extendedBlock constant specifies that we are using the fields that // follow this field in the structure launchParams.launchBlockID = extendedBlock; // length of the fields following this field (again, use a constant) launchParams.launchEPBLength = extendedBlockLen; // launch control flags // we want the existing program to continue, and not terminate // moreover, we want the function to determine the Finder flags itself launchParams.launchControlFlags = launchContinue + launchNoFileFlags; // FSSpec for the application to launch launchParams.launchAppSpec = &spec; // no parameters launchParams.launchAppParameters = NULL; err = LaunchApplication(&launchParams); if (err != noErr) { printf("failed to launch application (error %d).\n", err); exit(1); } printf("main: launched application, PSN = %lu_%lu\n", launchParams.launchProcessSN.highLongOfPSN, launchParams.launchProcessSN.lowLongOfPSN); printf("main: continuing\n"); exit(0); } $ gcc -Wall -o cpmtest CarbonProcessManager.c -framework Carbon $ ./cpmtest "Macintosh HD:Applications:Chess.app:Contents:MacOS:Chess" main: launched application, PSN = 0_21364737 main: continuing | 7.3.8. Carbon Multiprocessing Services The Carbon Multiprocessing Services (MP Services) API allows you to create preemptive tasks in an application. However, an MP task is not a Mach taskit is a thread that is preemptively scheduled by MP Services, which can run tasks independently on one or more processors, dividing processor time automatically among available tasks. An MP task maps to a pthread. Figure 736 shows an example of using MP Services. Figure 736. Using Carbon Multiprocessing Services // CarbonMultiprocessingServices.c #include <pthread.h> #include <CoreServices/CoreServices.h> OSStatus taskFunction(void *param) { printf("taskFunction: I am an MP Services task\n"); printf("taskFunction: my task ID is %#x\n", (int)MPCurrentTaskID()); printf("taskFunction: my pthread ID is %p\n", pthread_self()); return noErr; } int main() { MPQueueID queue; UInt32 param1, param2; UInt32 tParam1, tParam2; OSStatus status; MPTaskID task; // check for availability if (MPLibraryIsLoaded()) { printf("MP Services initialized\n"); printf("MP Services version %d.%d.%d.%d\n", MPLibrary_MajorVersion, MPLibrary_MinorVersion, MPLibrary_Release, MPLibrary_DevelopmentRevision); printf("%d processors available\n\n", (int)MPProcessorsScheduled()); } else printf("MP Services not available\n"); printf("main: currently executing task is %#x\n", (int)MPCurrentTaskID()); // create a notification queue status = MPCreateQueue(&queue); if (status != noErr) { printf("failed to create MP notification queue (error %lu)\n", status); exit(1); } tParam1 = 1234; tParam2 = 5678; printf("main: about to create new task\n"); printf("main: my pthread ID is %p\n", pthread_self()); // create an MP Services task status = MPCreateTask(taskFunction, // pointer to the task function (void *)0, // parameter to pass to the task (ByteCount)0, // stack size (0 for default) queue, // notify this queue upon termination &tParam1, // termination parameter 1 &tParam2, // termination parameter 2 kMPCreateTaskValidOptionsMask, &task); // ID of the newly created task if (status != noErr) { printf("failed to create MP Services task (error %lu)\n", status); goto out; } printf("main: created new task %#08x, now waiting\n", (int)task); // wait for the task to be terminated status = MPWaitOnQueue(queue, (void *)¶m1, (void *)¶m2, NULL, kDurationForever); printf("main: task terminated (param1 %lu, param2 %lu)\n", tParam1, tParam2); out: if (queue) MPDeleteQueue(queue); exit(0); } $ gcc -Wall -o mps CarbonMultiprocessingServices.c -framework Carbon $ ./mps MP Services initialized MP Services version 2.3.1.1 2 processors available main: currently executing task is 0xa000ef98 main: about to create new task main: my pthread ID is 0xa000ef98 main: created new task 0x1803200, now waiting taskFunction: I am an MP Services task taskFunction: my task ID is 0x1803200 taskFunction: my pthread ID is 0x1803200 main: task terminated (param1 1234, param2 5678) | Multitasking and Multiprocessing Multitasking is the ability to handle several tasks simultaneously, whereas multiprocessing is the ability of a system to use multiple processors simultaneously. Symmetric multiprocessing (SMP) is a configuration in which two or more processors are managed by one kernel, with both processors sharing the same memory and having equal status for almost all purposes. In an SMP system, any thread can run on any processor, unless a thread is programmatically bound to a particular processor. Multitasking can be either preemptive or cooperative. Preemption is the act of interrupting a currently running entity to give time to another runnable entity. In preemptive multitasking, the operating system can preempt one entity to run another, as needed. In cooperative multitasking, a running entity must give up control of the processorcooperativelyto allow others to run. Consequently, a runnable entity can receive processing time only if another entity allows it. | 7.3.9. The Carbon Thread Manager The Carbon Thread Manager allows you to create cooperatively scheduled threads, wherein each thread must explicitly relinquish control of the processor. This is done either by calling YieldToAnyThread(), which invokes the Carbon Thread Manager's scheduling mechanism to run the next available thread, or by calling YieldToThread(), which relinquishes control to a particular thread. Even though only one Carbon Thread Manager thread runs at a time within an application, each thread maps to a pthread. In our example program (Figure 737) for the Carbon Thread Manager, the main function will create several threads, mark them ready, and relinquish control to the first thread. Each thread will print its Carbon identifier, pthread identifier, and Mach port and then relinquish control to the next thread. The last thread on the list will relinquish control back to main, which will destroy all threads and exit. Figure 737. Creating Carbon Thread Manager threads // CarbonThreadManager.c #include <pthread.h> #include <mach/mach.h> #include <CoreServices/CoreServices.h> #define MAXTHREADS 8 static ThreadID mainThread; static ThreadID newThreads[MAXTHREADS] = { 0 }; voidPtr threadFunction(void *threadParam) { int i = (int)threadParam; printf("thread #%d: CTM %#08lx, pthread %p, Mach %#08x\n", i, newThreads[i], pthread_self(), mach_thread_self()); if (i == MAXTHREADS) YieldToThread(mainThread); else YieldToThread(newThreads[i + 1]); /* NOTREACHED */ printf("Whoa!\n"); return threadParam; } int main() { int i; OSErr err = noErr; // main thread's ID err = GetCurrentThread(&mainThread); for (i = 0; i < MAXTHREADS; i++) { err = NewThread( kCooperativeThread, // type of thread threadFunction, // thread entry function (void *)i, // function parameter (Size)0, // default stack size kNewSuspend, // options NULL, // not interested &(newThreads[i])); // newly created thread if (err || (newThreads[i] == kNoThreadID)) { printf("*** NewThread failed\n"); goto out; } // set state of thread to "ready" err = SetThreadState(newThreads[i], kReadyThreadState, kNoThreadID); } printf("main: created %d new threads\n", i); printf("main: relinquishing control to next thread\n"); err = YieldToThread(newThreads[0]); printf("main: back\n"); out: // destroy all threads for (i = 0; i < MAXTHREADS; i++) if (newThreads[i]) DisposeThread(newThreads[i], NULL, false); exit(err); } $ gcc -Wall -o ctm CarbonThreadManager.c -framework Carbon $ ./ctm main: created 8 new threads main: relinquishing control to next thread thread #0: CTM 0x1803200, pthread 0x1803200, Mach 0x001c03 thread #1: CTM 0x1803600, pthread 0x1803600, Mach 0x002e03 thread #2: CTM 0x1803a00, pthread 0x1803a00, Mach 0x003003 thread #3: CTM 0x1803e00, pthread 0x1803e00, Mach 0x003203 thread #4: CTM 0x1808400, pthread 0x1808400, Mach 0x003403 thread #5: CTM 0x1808800, pthread 0x1808800, Mach 0x003603 thread #6: CTM 0x1808c00, pthread 0x1808c00, Mach 0x003803 thread #7: CTM 0x1809000, pthread 0x1809000, Mach 0x003a03 main: back | |