QNX RTP Logo QNX Realtime Platform: Русский Портал QNX
Thursday, 20 Nov 2008 10:04
Меню

Проект OpenNET - все о Unix
Получение системной информации Print E-mail
Дмитрий Алексеев, dmi at qnx.org.ru

Большинству программ, тесно взаимодействующих с операционной системой, в которой они работают, необходимы данные о текущем состоянии системы. Это отладчики, компиляторы, системы мониторинга, да и просто полезные утилиты. То есть информация это во всех смыслах интересная и необходимая, однако методы ее получения от системы к системе (а для разработчика смена базовой ОС разработки - привычная практика, особенно при разработке многоплатформенных решений) различаются. И ?в? QNX это особенно ярко выражено.

В первую очередь это сетевая система ("Ага, удивил!", - усмехнется незнакомый с QNX читатель, но будет не прав). Архитектура QNX настолько прозрачна, а сетевая поддержка настолько глубоко интегрирована, что ни пользователю, ни разработчику, ни системе не видно никакой разницы между тем с каким устройством (файлом!) работать: с локальным или с удаленным. Любую программу можно "разделить" между узлами сети, вместо своего жесткого диска или CD-ROMа можно использовать соседский, а такие понятия, как "расшаренный ресурс" или "сетевой принтер" быстро забываются, как не очень приятный сон.

Сильно отличается архитектура диспетчера процессов. В большинстве ОС он спрятан где-то глубоко внутри ядра. В QNX же, менеджер процессов - вещь опциональная, хотя и практически всегда применяемая. А как же, спросите вы, такое чудо работает? Очень просто, как и все в QNX: системой управляет микроядро, для которого есть только несколько типовых понятий, как то: "сообщение", "поток", "барьер", "мутекс". Микроядро занимается диспетчеризацией потоков, а вся забота о процессах возложена на менеджер процессов procnto, построенный в виде менеджера ресурсов.

На менеджерах ресурсов базируется вся операционная система QNX. А обмен данными между ними и их клиентами - приложениями реализован посредством системных сообщений. За их доставку отвечает микроядро, в котором всего-то 31 килобайт кода. Отсюда кстати и работа всех процессов в полностью защищенном режиме, что так нехарактерно для операционных систем реального времени. И драйвера (тоже отдельные процессы), кстати, работают во втором, или, в крайнем случае, в первом кольце защиты. А нулевое принадлежит микроядру.

Архитектурная модель системы выполнена идеально, это, безусловно, лучшая на сегодняшний день организация ОС такого рода. Разработчики, "проникшиеся" гениальной очевидностью ее организации, часто после этого с тоской смотрят на вечнозеленый Linux и Embedded XP:

Однако надежная система требует ответственного подхода при разработке, ведь ОС - это лишь инструмент. Ответственный подход здесь - не только умение продумать и реализовать максимально эффективный алгоритм, но и вдумчиво протестировать свое детище. Об этом, собственно, и речь.

Самое для нас интересное - номер версии и тип системы, можно получить привычным для UNIX -разработчиков методом - функцией uname():

#include <sys/utsname.h>

struct utsname hostinfo;

uname(&hostinfo);
	
printf( "System: \t%s \nRelease: \t%s \nVersion \t%s \nMachine \t%s \nNodename: 
	\t%s\n",  
		hostinfo.sysname,	// название системы
		hostinfo.release,          // номер версии.
		hostinfo.version, 	// дата сборки
		hostinfo.machine,   	// тип архитектуры
		hostinfo.nodename	// сетевое имя компьютера.
);

В результате получим:

	System:		QNX
	Release: 	6.1.0
	Version: 	2001/08/23-19:38:50edt
	Machine: 	x86pc
	Nodename:	dmitry

Вывод этой программы аналогичен выводу программы uname с ключом '-a'. В QNX поддерживаются все стандартные для UNIX методы получения данных такого рода, но они не особо интересны, т.к. QNX это не совсем UNIX. Как я уже упоминал, менеджер процессов работает как драйвер псевдоустройства. За ним зарезервирован префикс "/proc". Это тоже привычная для UNIX-подобных систем практика, здесь как и везде, команда "ls /proc" выведет список идентификаторов процессов (PID) в системе. Но, при этом, есть и свои добавки, например, "размер" каталога "/proc" означает количество свободной оперативной памяти. В статье нет смысла приводить обработку ошибок, здесь и далее я ее опускаю

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>

	struct stat st;
	int fd;

	fd = open("/proc", O_RDONLY );
	fstat( fd, &st ); 
	printf( "Free memory: %u\n", st.st_size );

Мы узнали, что свободной памяти у нас с избытком и сразу же захотели узнать как с этим обстоят дела у соседа по сети J. Подправим программу (здесь node_name - сетевое имя):

	fd = open("/net/node_name/proc", O_RDONLY );

"Ух ты как!", - воскликнет читатель, - "а как же безопасность? Я что, могу читать любые файлы в сети?". Да. Понятия безопасности внутри QNX сети нет. Либо вы работаете совместно, в одной группе и целиком друг другу доверяете, либо выгружаете модуль QNET из памяти и отключаете его загрузку по умолчанию, оставляя только TCP/IP. Структура QNET прозрачна даже на уровне API. Рассмотрим, например, функцию SignalKill() - послать сигнал процессу

extern int SignalKill(_Uint32t __nd, pid_t __pid, int __tid, int __signo, int __code, int __value);

Первый ее параметр - дескриптор узла сети. В случае локального процесса это 0, иначе - идентификатор узла сети, получаемый из имени. В программах типа slay (послать сигнал процессу по имени, аналог скрипта killall) и pidin (получить информацию о процессах, расширенный аналог ps), принимающих имя узла сети в виде параметра после ключа '-n':

#include <sys/netmgr.h> 		/* заголовочный файл менеджера сети */
	unsigned nd; 			/* дескриптор узла */

	c = getopt(":n:..");   	/* обработка параметров командной строки (n: значит ключ -n с  параметром */
	switch(c) {
	...
	'n':    if (-1 == (nd = netmgr_strtond(optarg, 0))){ /* преобразуем значение параметра '-n' в дескриптор узла */
			exit(1);    			     /* указано неверное имя узла. */
		}
		/* сравним полученное  значение с именем локального узла */
		if (ND_NODE_CMP(nd, ND_LOCAL_NODE)==0) {
			/* это локальный узел */
		}
		break;
	}

Самые главные сведения о системе хранятся в структуре типа procfs_sysinfo, получение параметров из которой мы и рассмотрим в качестве первого примера:

#include <stdio.h>

#include <stdlib.h>
#include <sys/procfs.h> 		/* заголовочный файл менеджера процессов */
#include <fcntl.h>    
#include <time.h> 

	procfs_sysinfo *sysinfo;		/* структура с данными менеджера процессов */
	char buffer [100];			/* промежуточный буфер, см.  ниже */
	int fd;					/* файловый дескриптор каталога "/proc" */
	int i; 					/* итератор цикла */
	int total; 				/*  сколько у нас всего памяти ? */
	time_t boot_time; 			/*  время загрузки  */
	struct cpuinfo_entry cpu; 		/* структура, описывающая системный процессор (процессоры) */
	int mtype; 				/* тип архитектуры */
	int num_cpu; 				/* количество процессоров */

Менеджер процессов - драйвер псевдоустройства, для которого справедливы все стандартные операции для файлов, как, например, получение информации о файле (stat) или открытие его на чтение:


	fd =open("/proc", O_RDONLY);

Этим запросом мы на самом деле вовсе не открыли некий файл с диска своей (или чужой) машины, а создали программный канал, по которому наша программа будет обмениваться данными с менеджером процессов.

Структура procfs_sysinfo имеет переменную длину, которая зависит от параметров системы: типа архитектуры (поддерживаются MIPS, PPC, SH4, ARM и, конечно же, X86 ), количества процессоров и т.д. Поэтому сначала определим ее размер, который гарантированно расположен в первых ста байтах:

 
	sysinfo = (void *) buffer;

	if( -1 == devctl( fd, DCMD_PROC_SYSINFO, sysinfo, sizeof buffer, 0 ) ) {
		perror ("devctl");
		exit( 1 );
	}

Ага, devctl() - это что-то вроде привычного ioctl(), без чего ни один драйвер не может быть полностью функционален. Фактически это тоже сообщение менеджеру процессов, с вектором ввода/вывода (IOV) установленным на sysinfo. Действительный размер структуры содержится в переменной total_size, делаем поправку и читаем данные заново:

	i = sysinfo->total_size; 

	sysinfo=(void*)malloc(i);		 /* выделим необходимое количество памяти */
	if( -1 == devctl( fd, DCMD_PROC_SYSINFO, sysinfo, i, 0 ) ) {
		perror ("devctl");
		exit( 1 );
	}

Поскольку структура procfs_sysinfo разделена на несколько логических блоков (информация о процессорах, памяти, прерываниях, временных параметрах системы, аппаратуре и т.д.), для получения данных из нее реализован специальный макрос - _SYSPAGE_ENTRY, принимающий в качестве параметра имя структуры и название логического блока. Вот так выглядит код, получающий общее количество оперативной памяти в байтах:

	total = _SYSPAGE_ENTRY(sysinfo, system_private )->ramsize;

Время в секундах, когда система была загружена:

	boot_time = _SYSPAGE_ENTRY( sysinfo, qtime )->boot_time;
	printf( "booted at %s\n", ctime(&boot_time) );

Тип архитектуры. Для x86 - SYSPAGE_X86 и т.д.

	mtype = sysinfo->type; 

Количество процессоров:

	num_cpu = sysinfo->num_cpu;

Пройдемся по всем процессорам и посмотрим, что они из себя представляют:

	for( i=1, cpu =_SYSPAGE_ENTRY( sysinfo, cpuinfo ); i<= sysinfo->num_cpu; i++, cpu++ )
	{
		printf( "CPU#%d: ", i );  		/* Порядковый номер процессора  */
		printf( "%u ", cpu->cpu ); 		/* Тип процессора. В моем случаем 686 */
		  /* Строковое описание процессора. В моем случае Intel Pentium Celeron Stepping 5 */
		printf( "%s ", &_SYSPAGE_ENTRY( sysinfo, strings )->data[cpu->name] );
		  /* тактовая частота процессора */
		printf( "%d Mhz ", cpu->speed );
		  /* флаги процессора */
		printf( "FPU: %s MMU: %s", 
		cpu->flags & CPU_FLAG_FPU ? "yes":"no",     /* FPU  */
		cpu->flags & CPU_FLAG_MMU ? "yes": "no" );  /* MMU  */
	} //for

Распечатаем список основных блоков памяти:

	struct meminfo_entry *meminfo; 

	meminf=(struct meminfo_entry *)SYSPAGE_ENTRY(meminfo);
	while ((meminf != 0) && (meminf->type != MEMTYPE_NONE)) {
		 printf( "addr: %p size %x type: %d \n", 
   		 	meminf->addr, 		/* начальный адрес блока памяти */
	    		meminf->size,  		/* размер блока  */
    			meminf->type ); 		/* его тип */
    		 meminf++;  		/* переходим к следующему блоку */
	} //while

Вывод результатов работы этой программы вызывает недоумение, не правда ли? Сначала идет блок типа MEMTYPE_RAM(1), потом MEMTYPE_IMAGEFSYS(2), MEMTYPE_BOOTRAM(3), а потом снова MEMTYPE_RAM. Да, QNX хранит в своей памяти образ ядра (.boot или qnxbasedma.ifs), потому как драйвер файловой системы загружается не всегда. И пока я этого не увидел, я не мог понять, почему вызов mmap() в недоделанном lxrun возвращает ENOTSUP.

Кроме перечисленных типов блоков памяти есть MEMTYPE_IOMEM (блоки памяти для работы с портами ввода/вывода, это не актуально для x86) и MEMTYPE_FLASHFSYS.

Здесь описана лишь малая часть из того, что можно "выжать" из структуры procfs_sysinfo, но этого и заголовочных файлов вполне достаточно, чтобы разобраться с механизмом devctl() и спецификой procnto.

Теперь напишем программу, запрашивающую информацию о всех процессах и потоках в системе:

#include <dirent.h> 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include <fcntl.h>
#include <sys/procfs.h>

int main() {
    DIR * dir;  		/* дескриптор каталога 			 */
    struct dirent dirent;  	/* дескриптор элемента каталога   	 */
    int fd;			/* файловый дескриптор (для процесса )   */
    pid_t pid;			/* PID, идентификатор процесса		 */
    char buf[100];		/* буфер для формирования строки	 */
    procfs_info info;    	/* информация о процессе		 */
    procfs_status status;	/* информация о потоке			 */
    int errcode;    		/* код ошибки				 */

    if (!(dir = opendir( "/proc"))) {		/* пытаемся открыть каталог	*/
	exit(EXIT_FAILURE);	
    }
    while (dirent = readdir(dir)) {		/* ищем элемент каталога	*/
	if(!isdigit(dirent->d_name[0]))  	/* это точно процесс ? 		*/
	    continue;
	
	pid = atoi(dirent->d_name);    		/* преобразуем в число		*/
	
	if (pid <= 1 )				/* либо это чушь, либо procnto, в     
						   	любом случае  опрашивать это не имеет   
						    	смысла		        */
	    continue;
   
	sprintf( buf, "/proc/%d/as", pid );	/* ...именно так			*/ 

            /*  пробуем его открыть и запросить информацию */
	if (-1 == (fd = open(buf, O_RDONLY)) || 
	    EOK != devctl(fd, DCMD_PROC_INFO, &info, sizeof info, 0 ) ) {
	    exit(EXIT_FAILURE);

   } //while

В info находится информация об опрошенном процессе. В этой структуре 29 параметров, и я не буду описывать ее здесь. Описание можно посмотреть в заголовочном файле debug.h, структура _debug_process_t.

	/* получим данные по всем потокам процесса */
    	for( status.tid =1 ; ; ++status.tid ) {
	    /* запрос на информацию по состоянию потока */
	    errcode = devctl(fd, DCMD_PROC_TIDSTATUS, &status, sizeof(status), 0);

    /* если потоков больше нет, то выходим из цикла
                    и переходим к следующему процессу                   */
	    if (errcode == ESRCH) {	
		break;
	    }
                /* произошла ошибка.  */
	    if( errcode )
		exit(EXIT_FAILURE);
	    else

В этой структуре, как и в _debug_process_t, слишком много параметров, чтобы рассматривать их в этой статье. Так что для примера распечатаем только идентификатор потока.

		printf( "tid: %d \n", status.tid );
	   } // for
    }
    close(fd);
    closedir(dir);    
} // main()

Это, конечно же, только малая часть. Точно таким же методом можно получить список открытых файловых дескрипторов процесса, сохраненные регистры процессора или сопроцессора, стек процесса, список переменных окружения и т.д.

[Вернуться к списку]
©   2000-2003 Команда проекта QNX.ORG.RU // QNX.ORG.RU Team
Авторы проекта: Дмитрий Алексеев [dmi] и Дмитрий Васильев. Техническое сопровождение проекта: Игорь Сорокин [isorokin]. Информационное сопровождение: Дмитрий Алексеев [dmi]
QNX - зарегистрированная торговая марка QNX Software Systems, Ltd., Canada. Остальные упоминаемые на сайте торговые марки и логотипы являются исключительно собственностью их уважаемых владельцев. Ничьи права не затронуты. Материалы сайта не могут быть скопированы и где-либо использованы в той или иной форме без письменного разрешения разработчиков сайта.
Powered by Mambo Open Source