• Александр Черный
  • Блог
  • Проекты
  • О себе
  • RSS
10 августа 2010

Как получить список всех процессов в Mac OS X

Привет, мир. Затеял писать одну утилиту. Нужно было получить список всех процессов в Mac OS X. Рабочий язык — Objective-C. Обнаружил, что не все так просто. Здесь приведу полученное решение и путь к нему.

До это Objective-C я видел дважды. На семинаре по iPhone SDK на втором HackDay и после, уже в самостоятельных поисках. Еще есть стойкое подозрение, что в Рунете вообще нет информации на эту тему. По крайней мере, я не нашел даже намека. Не судите строго.

Для начала надо понять, что подразумевается под процессом. Если вам нужно получить список запущенных приложений, надо смотреть в сторону метода GetNextProcess. Он вернет список всех запущенных приложений для Carbon, Cocoa и Classic. Но не вернет список демонов. Для православных братьев с Windows: не вернет службы.

Но мы же правильные парни. Правильные парни хотят получить список всех запущенных процессов, что для BSD системы выполнимо, если привлечь sysctl. Возвращаемая структура kinfo_proc будет содержать всю информацию. Например, идентификатор процесса хранится в kp_proc.p_pid, а его имя в kp_proc.p_comm. Для обращения к syscrl не нужны никакие особенные привилегии, то есть любой пользователь может получить список всех процессов в системе. Для поклонников UNIX way замечу, что можно использовать ps, а потом разбирать и интерпретировать то, что команда даст на выходе. Не думаю, что это будет эффективно. Но вдруг. О других способах ничего не знаю.

Прежде, чем начать, чуть-чуть о задействованных заголовочных файлах. Есть в стандартной библиотеке C макрос assert(). Этот макрос разворачивается в if. Если аргумент в assert принимает нулевое значение, то программа прерывается с помощью abort() и в stderr выводится сообщение. Файл errno.h нужен, так как в нем определена ENOMEM — недостаточно памяти. Из stdlib.h нам понадобятся malloc(), free() и abort(). Наконец, stdbool.h содержит несколько макросов для работы с bool.

Последнее отступление перед делом. Если вам нужна информацию о текущем процессе, то есть о себе, можно использовать такой вот код.

NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSString *processName = [processInfo processName];
int processID = [processInfo processIdentifier];
NSLog(@"Process Name: '%@' Process ID:'%d'", processName, processID);

Но если дело серьезнее, как описано, тогда надо быть замысловатее.

#import <assert.h>
#import <errno.h>>
#import <stdbool.h>>
#import <stdlib.h>>
#import <sys/sysctl.h>>
#import <Foundation/Foundation.h>>

typedef struct kinfo_proc kinfo_proc;

static int GetBSDProcessList(kinfo_proc **processList, size_t *processCount) {
  kinfo_proc *buffer = NULL;
  size_t length;
  int sysctlResult;
  bool isDone = false;
  static const int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
  
  assert(processList != NULL);
  assert(*processList == NULL);
  assert(processCount != NULL);
  
  *processCount = 0;
  
  do {
    assert(buffer == NULL);
    
    // Вызываем sysctl со значением буфера NULL
    length = 0;
    sysctlResult = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, NULL, &length, NULL, 0);
    if (sysctlResult == -1) {
      sysctlResult = errno;
    }
    
    // Выделение буфера соответствующего размера на основе результатов из предыдущего вызова
    if (sysctlResult == 0) {
      buffer = malloc(length);
      if (buffer == NULL) {
        sysctlResult = ENOMEM;
      }
    }
    
    // Вызываем sysctl с новым буфером.
    // Если получим ошибку ENOMEM, надо обнулить буфер и повторить все снова
    if (sysctlResult == 0) {
      sysctlResult = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, buffer, &length, NULL, 0);
      if (sysctlResult == -1) {
        sysctlResult = errno;
      }
      if (sysctlResult == 0) {
        isDone = true;
      } else if (sysctlResult == ENOMEM) {
        assert(buffer != NULL);
        free(buffer);
        buffer = NULL;
        sysctlResult = 0;
      }
    }
  } while (sysctlResult == 0 && !isDone);
  
  // Обнуление буфера
  if (sysctlResult != 0 && buffer != NULL) {
    free(buffer);
    buffer = NULL;
  }
  
  *processList = buffer;
  if (sysctlResult == 0) {
    *processCount = length / sizeof(kinfo_proc);
  }
  
  assert((sysctlResult == 0) == (*processList != NULL));
  
  return sysctlResult;
}

int main (int argc, const char * argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  kinfo_proc *allProcesses = 0;
  size_t numberOfProcesses;
    
  GetBSDProcessList(&allProcesses, &numberOfProcesses);
  
  for(int i = 0; i < numberOfProcesses; i++ ) {
    NSLog(@"%d: %sn", allProcesses[i].kp_proc.p_pid, allProcesses[i].kp_proc.p_comm);
  }
  
  free(allProcesses);
  
  [pool drain];
  return 0;
}

Описанная функция вернет список всех процессов. Результат будет в processList, а общее количество процессов в processCount. В случае успеха функция вернет 0, в случае ошибки — номер ошибки для BSD. Память под список будет выделена внутри функции. Не забудьте ее освободить.

Начнем с вызова sysctl. Значение result — NULL. Значение length — 0. В случае успешного выполнения мы получим значение для length, а значит, можно выделить память под result. После этого повторим вызов sysctl с новым буфером. Если прошло успешно, цель достигнута. Если имеет место недостаток памяти (ENOMEM), придется обнулить result и повторить цикл. Обратите внимание, вызов sysctl снова будет с NULL. Это необходимо, потому что в случае ошибки ENOMEM значение length устанавливается равным количеству уже возвращенных данных, а не количеству данных, которые могли бы быть возвращены.

В main() можно видеть объявление указателя allProcesses на структуру, которая будет хранить данные о процессах. Количество процессов будет в переменной numberOfProcesses. Вывод информации в цикле. Не забудьте освободить память для allProcesses.

objective-c   mac os   процессы   

Ваш комментарий


(не будет опубликован)


© Александр Черный, 2009–2023

Служебный вход

Работает на YAPSE, β