6.3. A Flavor of the Mach APIs Let us look at a few simple examples of using the Mach APIs. These will serve as a prelude to more complex or subsystem-specific examples that we will see later in this chapter and in subsequent chapters. Documentation for most Mach calls exported by the xnu kernel is available in the osfmk/man/ directory within the xnu package. You might find it useful to test API-based examples while referring to the API documentation. 6.3.1. Displaying Host Information The host_info() Mach call retrieves information about a host, such as the type and number of processors installed, the number of processors currently available, and the memory size. As is the case with many Mach "info" calls, host_info() takes a flavor argument, which specifies the kind of information to be retrieved. For example, host_info() accepts the HOST_BASIC_INFO, HOST_SCHED_INFO, and HOST_PRIORITY_INFO flavors as arguments to return basic, scheduler-related, and scheduler-priority-related information, respectively, from the kernel. Besides host_info(), other calls such as host_kernel_version(), host_get_boot_info(), and host_page_size() can be used to retrieve miscellaneous information. Figure 61 shows an example of using the host_info() call. Figure 61. Retrieving basic host information using Mach calls // host_basic_info.c #include <stdio.h> #include <stdlib.h> #include <mach/mach.h> #define EXIT_ON_MACH_ERROR(msg, retval) \ if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); exit((retval)); } int main() { kern_return_t kr; // the standard return type for Mach calls host_name_port_t myhost; kernel_version_t kversion; host_basic_info_data_t hinfo; mach_msg_type_number_t count; char *cpu_type_name, *cpu_subtype_name; vm_size_t page_size; // get send rights to the name port for the current host myhost = mach_host_self(); kr = host_kernel_version(myhost, kversion); EXIT_ON_MACH_ERROR("host_kernel_version", kr); count = HOST_BASIC_INFO_COUNT; // size of the buffer kr = host_info(myhost, // the host name port HOST_BASIC_INFO, // flavor (host_info_t)&hinfo, // out structure &count); // in/out size EXIT_ON_MACH_ERROR("host_info", kr); kr = host_page_size(myhost, &page_size); EXIT_ON_MACH_ERROR("host_page_size", kr); printf("%s\n", kversion); // the slot_name() library function converts the specified // cpu_type/cpu_subtype pair to a human-readable form slot_name(hinfo.cpu_type, hinfo.cpu_subtype, &cpu_type_name, &cpu_subtype_name); printf("cpu %s (%s, type=0x%x subtype=0x%x " "threadtype=0x%x)\n", cpu_type_name, cpu_subtype_name, hinfo.cpu_type, hinfo.cpu_subtype, hinfo.cpu_threadtype); printf("max_cpus %d\n", hinfo.max_cpus); printf("avail_cpus %d\n", hinfo.avail_cpus); printf("physical_cpu %d\n", hinfo.physical_cpu); printf("physical_cpu_max %d\n", hinfo.physical_cpu_max); printf("logical_cpu %d\n", hinfo.logical_cpu); printf("logical_cpu_max %d\n", hinfo.logical_cpu_max); printf("memory_size %u MB\n", (hinfo.memory_size >> 20)); printf("max_mem %llu MB\n", (hinfo.max_mem >> 20)); printf("page_size %u bytes\n", page_size); exit(0); } $ gcc -Wall -o host_basic_info host_basic_info.c $ ./host_basic_info # Power Mac G5 Quad 2.5GHz Darwin Kernel Version 8.5.0: ... root:xnu-792.6.61.obj~1/RELEASE_PPC cpu ppc970 (PowerPC 970, type=0x12 subtype=0x64 threadtype=0x0) max_cpus 4 avail_cpus 4 physical_cpu 4 physical_cpu_max 4 logical_cpu 4 logical_cpu_max 4 memory_size 2048 MB max_mem 4096 MB page_size 4096 bytes $ ./host_basic_info # iMac Core Duo 1.83GHz Darwin Kernel Version 8.5.1: ... root:xnu-792.8.36.obj~1/RELEASE_I386 cpu i486 (Intel 80486, type=0x7, subtype=0x4, threadtype=0x0) max_cpus 2 avail_cpus 2 ... page_size 4096 bytes | Note in Figure 61 that as discussed in Chapter 5, the memory_size value reported by Mach is pinned to 2GB on a machine with more than 2GB of physical memory. 6.3.2. Accessing the Kernel's Clock Services The kernel provides clock services with different clock types such as system, calendar, and real time. Accessing these services involves acquiring send rights to their ports and sending messages requesting the clocks' attributes or functionality. Figure 62 shows a program that retrieves the attributes and current time values from the kernel's various clocks. Figure 62. Retrieving clock attributes and time values in Mach // host_clock.c #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <mach/mach.h> #include <mach/clock.h> #define OUT_ON_MACH_ERROR(msg, retval) \ if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); goto out; } int main() { kern_return_t kr; host_name_port_t myhost; clock_serv_t clk_system, clk_calendar, clk_realtime; natural_t attribute[4]; mach_msg_type_number_t count; mach_timespec_t timespec; struct timeval t; myhost = mach_host_self(); // Get a send right to the system clock's name port kr = host_get_clock_service(myhost, SYSTEM_CLOCK, (clock_serv_t *)&clk_system); OUT_ON_MACH_ERROR("host_get_clock_service", kr); // Get a send right to the calendar clock's name port kr = host_get_clock_service(myhost, CALENDAR_CLOCK, (clock_serv_t *)&clk_calendar); OUT_ON_MACH_ERROR("host_get_clock_service", kr); // Get a send right to the real-time clock's name port kr = host_get_clock_service(myhost, REALTIME_CLOCK, (clock_serv_t *)&clk_realtime); OUT_ON_MACH_ERROR("host_get_clock_service", kr); //// System clock count = sizeof(attribute)/sizeof(natural_t); // Get the clock's resolution in nanoseconds kr = clock_get_attributes(clk_system, CLOCK_GET_TIME_RES, (clock_attr_t)attribute, &count); OUT_ON_MACH_ERROR("clock_get_attributes", kr); // Get the current time kr = clock_get_time(clk_system, ×pec); OUT_ON_MACH_ERROR("clock_get_time", kr); printf("System clock : %u s + %u ns (res %u ns)\n", timespec.tv_sec, timespec.tv_nsec, attribute[0]); //// Real-time clock count = sizeof(attribute)/sizeof(natural_t); kr = clock_get_attributes(clk_realtime, CLOCK_GET_TIME_RES, (clock_attr_t)&attribute, &count); OUT_ON_MACH_ERROR("clock_get_attributes", kr); kr = clock_get_time(clk_realtime, ×pec); OUT_ON_MACH_ERROR("clock_get_time", kr); printf("Realtime clock: %u s + %u ns (res %u ns)\n", timespec.tv_sec, timespec.tv_nsec, attribute[0]); //// Calendar clock count = sizeof(attribute)/sizeof(natural_t); kr = clock_get_attributes(clk_calendar, CLOCK_GET_TIME_RES, (clock_attr_t)&attribute, &count); OUT_ON_MACH_ERROR("clock_get_attributes", kr); kr = clock_get_time(clk_calendar, ×pec); gettimeofday(&t, NULL); OUT_ON_MACH_ERROR("clock_get_time", kr); printf("Calendar clock: %u s + %u ns (res %u ns)\n", timespec.tv_sec, timespec.tv_nsec, attribute[0]); printf("gettimeofday : %ld s + %d us\n", t.tv_sec, t.tv_usec); out: // Should deallocate ports here for cleanliness mach_port_deallocate(mach_task_self(), myhost); mach_port_deallocate(mach_task_self(), clk_calendar); mach_port_deallocate(mach_task_self(), clk_system); mach_port_deallocate(mach_task_self(), clk_realtime); exit(0); } $ gcc -Wall -o host_clock host_clock.c $ ./host_clock System clock : 134439 s + 840456243 ns (res 10000000 ns) Realtime clock: 134439 s + 841218705 ns (res 10000000 ns) Calendar clock: 1104235237 s + 61156000 ns (res 10000000 ns) gettimeofday : 1104235237 s + 61191 us | 6.3.3. Using a Clock Service to Ring an Alarm Having seen how to acquire send rights to a clock service's port, we can use these rights to request the service to ring an alarm at a specified time. The clock will notify us by sending an IPC message when the alarm is fired. The program shown in Figure 63 sets an alarm to ring after 2.5 seconds and waits for the alarm message to arrive on a port it allocated. Figure 63. Setting an alarm using Mach calls // host_alarm.c #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <mach/mach.h> #include <mach/clock.h> #define OUT_ON_MACH_ERROR(msg, retval) \ if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); goto out; } // Structure for the IPC message we will receive from the clock typedef struct msg_format_recv_s { mach_msg_header_t header; int data; mach_msg_trailer_t trailer; } msg_format_recv_t; int main() { kern_return_t kr; clock_serv_t clk_system; mach_timespec_t alarm_time; clock_reply_t alarm_port; struct timeval t1, t2; msg_format_recv_t message; mach_port_t mytask; // The C library optimized this call by returning the task port's value // that it caches in the mach_task_self_ variable mytask = mach_task_self(); kr = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, (clock_serv_t *)&clk_system); OUT_ON_MACH_ERROR("host_get_clock_service", kr); // Let us set the alarm to ring after 2.5 seconds alarm_time.tv_sec = 2; alarm_time.tv_nsec = 50000000; // Allocate a port (specifically, get receive right for the new port) // We will use this port to receive the alarm message from the clock kr = mach_port_allocate( mytask, // the task acquiring the port right MACH_PORT_RIGHT_RECEIVE, // type of right &alarm_port); // task's name for the port right OUT_ON_MACH_ERROR("mach_port_allocate", kr); gettimeofday(&t1, NULL); // Set the alarm kr = clock_alarm(clk_system, // the clock to use TIME_RELATIVE, // how to interpret alarm time alarm_time, // the alarm time alarm_port); // this port will receive the alarm message OUT_ON_MACH_ERROR("clock_alarm", kr); printf("Current time %ld s + %d us\n" "Setting alarm to ring after %d s + %d ns\n", t1.tv_sec, t1.tv_usec, alarm_time.tv_sec, alarm_time.tv_nsec); // Wait to receive the alarm message (we will block here) kr = mach_msg(&(message.header), // the message buffer MACH_RCV_MSG, // message option bits 0, // send size (we are receiving, so 0) message.header.msgh_size,// receive limit alarm_port, // receive right MACH_MSG_TIMEOUT_NONE, // no timeout MACH_PORT_NULL); // no timeout notification port // We should have received an alarm message at this point gettimeofday(&t2, NULL); OUT_ON_MACH_ERROR("mach_msg", kr); if (t2.tv_usec < t1.tv_usec) { t1.tv_sec += 1; t1.tv_usec -= 1000000; } printf("\nCurrent time %ld s + %d us\n", t2.tv_sec, t2.tv_usec); printf("Alarm rang after %ld s + %d us\n", (t2.tv_sec - t1.tv_sec), (t2.tv_usec - t1.tv_usec)); out: mach_port_deallocate(mytask, clk_system); // Release user reference for the receive right we created mach_port_deallocate(mytask, alarm_port); exit(0); } $ gcc -Wall -o host_alarm host_alarm.c $ ./host_alarm Current time 1104236281 s + 361257 us Setting alarm to ring after 2 s + 50000000 ns Current time 1104236283 s + 412115 us Alarm rang after 2 s + 50858 us | 6.3.4. Displaying Host Statistics The host_statistics() call can be used to retrieve statistical information about processor and virtual memory usage on a system-wide basis. Figure 64 shows an example of using this call. Figure 64. Using Mach calls to retrieve scheduling and virtual memory statistics // host_statistics.c #include <stdio.h> #include <stdlib.h> #include <mach/mach.h> // Wrapper function with error checking kern_return_t do_host_statistics(host_name_port_t host, host_flavor_t flavor, host_info_t info, mach_msg_type_number_t *count) { kern_return_t kr; kr = host_statistics(host, // control port for the host flavor, // type of statistics desired (host_info_t)info, // out buffer count); // in/out size of buffer if (kr != KERN_SUCCESS) { (void)mach_port_deallocate(mach_task_self(), host); mach_error("host_info:", kr); exit(1); } return kr; } int main() { kern_return_t kr; host_name_port_t host; mach_msg_type_number_t count; vm_size_t page_size; host_load_info_data_t load_info; host_cpu_load_info_data_t cpu_load_info; vm_statistics_data_t vm_stat; host = mach_host_self(); count = HOST_LOAD_INFO_COUNT; // Get system loading statistics kr = do_host_statistics(host, HOST_LOAD_INFO, (host_info_t)&load_info, &count); count = HOST_VM_INFO_COUNT; // Get virtual memory statistics kr = do_host_statistics(host, HOST_VM_INFO, (host_info_t)&vm_stat, &count); count = HOST_CPU_LOAD_INFO_COUNT; // Get CPU load statistics kr = do_host_statistics(host, HOST_CPU_LOAD_INFO, (host_info_t)&cpu_load_info, &count); kr = host_page_size(host, &page_size); printf("Host statistics:\n"); // (average # of runnable processes) / (# of CPUs) printf("Host load statistics\n"); printf(" time period (sec) %5s%10s%10s\n", "5", "30", "60"); printf(" load average %10u%10u%10u\n", load_info.avenrun[0], load_info.avenrun[1], load_info.avenrun[2]); printf(" Mach factor %10u%10u%10u\n", load_info.mach_factor[0], load_info.mach_factor[1], load_info.mach_factor[2]); printf("\n"); printf("Cumulative CPU load statistics\n"); printf(" User state ticks = %u\n", cpu_load_info.cpu_ticks[CPU_STATE_USER]); printf(" System state ticks = %u\n", cpu_load_info.cpu_ticks[CPU_STATE_SYSTEM]); printf(" Nice state ticks = %u\n", cpu_load_info.cpu_ticks[CPU_STATE_NICE]); printf(" Idle state ticks = %u\n", cpu_load_info.cpu_ticks[CPU_STATE_IDLE]); printf("\n"); printf("Virtual memory statistics\n"); printf(" page size = %u bytes\n", page_size); printf(" pages free = %u\n", vm_stat.free_count); printf(" pages active = %u\n", vm_stat.active_count); printf(" pages inactive = %u\n", vm_stat.inactive_count); printf(" pages wired down = %u\n", vm_stat.wire_count); printf(" zero fill pages = %u\n", vm_stat.zero_fill_count); printf(" pages reactivated = %u\n", vm_stat.reactivations); printf(" pageins = %u\n", vm_stat.pageins); printf(" pageouts = %u\n", vm_stat.pageouts); printf(" translation faults = %u\n", vm_stat.faults); printf(" copy-on-write faults = %u\n", vm_stat.cow_faults); printf(" object cache lookups = %u\n", vm_stat.lookups); printf(" object cache hits = %u (hit rate %2.2f %%)\n", vm_stat.hits, 100 * (double)vm_stat.hits/(double)vm_stat.lookups); exit(0); } $ gcc -Wall -o host_statistics host_statistics.c $ ./host_statistics Host statistics: Host load statistics time period (sec) 5 30 60 load average 276 233 70 Mach factor 1685 1589 1609 Cumulative CPU load statistics User state ticks = 109098 System state ticks = 41056 Nice state ticks = 535 Idle state ticks = 1974855 Virtual memory statistics page size = 4096 bytes pages free = 434154 pages active = 70311 pages inactive = 236301 pages wired down = 45666 zero fill pages = 2266643 pages reactivated = 0 pageins = 55952 pageouts = 0 translation faults = 4549671 copy-on-write faults = 83912 object cache lookups = 36028 object cache hits = 19120 (hit rate 53.07 %) | |