КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Семафор (счетный)
Разделяемая память
Разделяемая память — чрезвычайно важный и широко используемый POSIX-механизм обмена данными большого объема (впрочем, не обязательно большого) между процессами. Использовать этот механизм можно, к примеру, выполнив на стороне каждого из взаимодействующих процессов такие действия: 1. С помощью функции shm_open() создается или открывается существующий регион памяти, который будет разделяться. 2. С помощью функции shm_ctl() задаются нужные атрибуты разделяемого региона. 3. С помощью функции mmap() на разделяемый регион памяти отображается фрагмент адресного пространства процесса, после чего с этим фрагментом выполняются необходимые операции (например, чтения или записи данных). 4. Для отмены отображения адресного пространства процесса на разделяемый регион используется функция unmap(), для закрытия разделяемого региона — функция shm_close(), для уничтожения — функция shm_unlink().
Рассмотрим пример из двух программ — shm_creator и shm_user.
Пример работает так: 1. Запускается программа shm_creator, которая создает регион разделяемой памяти, задает его параметры и отображает на него некий буфер, содержащий текстовую строку. 2. Запускается программа shm_user, которая отображает регион разделяемой памяти, созданный программой shm_creator, на свой буфер и печатает содержимое этого буфера.
Приведем исходные тексты обеих программ. Текст файла shm_creator.c выглядит так: #include<stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <inttypes.h> int main() { int fd, status; void* buffer; fd = shm_open("/swd_es", O_RDWR | O_CREAT, 0777); if(fd == -1) { perror("shm_creator"); return EXIT_FAILURE; } status = ftruncate(fd, 100); if (status!=0) { perror("shm_creator"); return EXIT_FAILURE; } buffer=mmap(0,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if (buffer == MAP_FAILED) { perror("shm_creator"); return EXIT_FAILURE; } sprintf(buffer, "It's a nice day today, isn't it?"); printf("shm_creator: %s\n", buffer); return EXIT_SUCCESS; } Итак, сначала вызываем функцию shm_open(): fd = shm_open("/swd_es", O_RDWR|O_CREAT, 0777); Первый аргумент "/swd_es" — имя региона разделяемой памяти (или, как еще говорят, разделяемого объекта памяти);
Примечание
Обратите внимание, что когда имя разделяемого объекта начинается с символа /, объект будет помещен в служебный "каталог" /dev/shmem. То есть реальное имя создаваемого нами региона — /dev/shmem/swd_es. Второй аргумент представляет собой битовую маску из нескольких флагов, к этим флагам относятся: .. O_RDONLY — открыть объект только для чтения; .. O_RDWR — открыть объект для чтения и записи; .. O_CREAT — создать разделяемый объект с режимом доступа, заданным третьим аргументом функции shm_open(). Если объект уже существует, то флаг O_CREAT игнорируется, за исключением случаев, когда указан еще и флаг O_EXCL; .. O_EXCL — этот флаг используют совместно с флагом O_CREAT. В результате, если разделяемый объект памяти уже существует, то функция shm_open() завершится с ошибкой; .. O_TRUNC — этот флаг работает, когда объект уже существует и успешно открыт для чтения/записи. При этом размер объекта становится равным нулю (режим доступа и идентификатор владельца сохраняются). Третий аргумент задает атрибуты доступа к разделяемому объекту. Функция вернет файловый дескриптор fd, который в последующем и будет использоваться для доступа к данному разделяемому объекту. Теперь нужно сделать, чтобы разделяемый объект имел нужный размер и параметры. Для этого используем функцию shm_ctl(): ftruncate(fd, 100);
В качестве первого аргумента используется тот самый идентификатор объекта разделяемой памяти fd, который вернула функция shm_open();
Примечание
Иногда вместо функции ftrancate() используют функцию shm_ctl(). Теперь созданный объект, имеющий нужный размер, необходимо отобразить на виртуальное адресное пространство нашего процесса:
buffer = mmap(0, 100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
Первый и последний аргументы в нашем случае не нужны — они требуются при работе с физической памятью. Второй аргумент (100) указывает, какой величины фрагмент разделяемого объекта стоит отобразить на адресное пространство процесса (мы отобразили весь объект). Третий аргумент представляет собой битовую маску, которая может содержать несколько флагов: .. PROT_EXEC — разделяемый объект доступен для исполнения; .. PROT_NOCACHE — не кэшировать разделяемый объект; .. PROT_NONE — разделяемый объект недоступен; .. PROT_READ — разделяемый объект доступен для чтения; ..PROT_WRITE — разделяемый объект доступен для записи. Четвертый аргумент определяет режим отображения региона. В нашем случае лучше задать MAP_SHARED (остальные режимы используются для работы с физической памятью). Пятый аргумент — идентификатор разделяемого объекта. Функция mmap() возвращает указатель на область виртуальной памяти процесса, на который отображен разделяемый объект (buffer). С полученым указателем мы можем поступать, как нам вздумается. Все, что мы запишем по этому адресу, будет отображено на разделяемый объект (конечно же, столько байт, сколько мы задали функции mmap()). Запишем в буфер текстовую строку:
sprintf(buffer, "It's a nice day today, isn't it?");
Теперь посмотрим на исходный текст программы shm_user.c: #include<stdlib.h> #include <fcntl.h> #include <sys/mman.h> int main() { int fd; char *buffer; fd = shm_open("/swd_es", O_RDONLY, 0777); if(fd == -1) { perror("shm_user"); return EXIT_FAILURE; } buffer = mmap(0, 100, PROT_READ, MAP_SHARED, fd, 0); if (buffer == MAP_FAILED) { perror("shm_user"); return EXIT_FAILURE; } printf("shm_user: %s\n", buffer); munmap(buffer, sizeof (buffer)); return EXIT_SUCCESS; } Как видно из текста программы, для получения доступа к разделяемому объекту снова используется функция shm_open(). Для того чтобы отобразить разделяемый регион на адресное пространство процесса, используется функция mmap(). В результате получаем указатель buffer на область виртуальной памяти процесса, который можно использовать. Распечатаем содержимое разделяемого объекта: printf("shm_user: %s\n", buffer);
По аналогии мы можем передавать между процессами любые структуры данных. Но помните о том, что за правильность интерпретации данных, содержащихся в разделяемой памяти, отвечаете вы сами. Хорошо если бы вы догадались спросить: а как процесс, считывающий данные из разделяемой памяти, определяет, что запись данных уже закончена и данные готовы для чтения? Ответ: никак. Чтобы избежать нарушений целостности данных, нам нужно использовать механизмы синхронизации. Основным POSIX-механизмом синхронизации процессов являются семафоры.
Семафор является весьма специфическим (в сравнении с прочими) для ОС QNX средством синхронизации, и хотя функции работы с ним также определяются стандартом POSIX, даже семантика этих функций отличается от всех прочих объектов синхронизации.
Примечание Функции работы с семафором определяются стандартом P0SIX 1003.1 (1993) расширения реального времени, а не стандартом POSIX 1003b (1995), которым определены pthread_* и все другие примитивы синхронизации; соответственно функции манипулирования семафорами не начинаются с префикса pthread_*.
В классической работе Дейкстры [10] семафор определяется как объект, над которым можно провести две атомарные операции: инкремент и декремент внутреннего счетчика - при условии, что внутренний счетчик не может принимать значение меньше нуля. Если некий поток пытается уменьшить на единицу значение внутреннего счетчика семафора, значение которого уже равно нулю, то этот поток блокируется до тех пор, пока внутренний счетчик семафора не примет значение, равное 1 или больше (посредством воздействия на него других потоков). Разблокированный поток сможет осуществить декремент нового значения. Принято рассматривать семафоры двух видов: бинарные, счетчик которых может принимать только значения 0 либо 1, и счетные, или простые, счетчик которых может принимать большие положительные значения. В QNX 6.2.1 максимальное значение счетчика определяется переменной SEM_VALUE_MAX, значение которой равно 32 767. Отметим важный момент: семафор является наиболее простым и соответственно наиболее универсальным средством синхронизации. И классические решения задач раздельного использования ресурсов, предложенные в теории первоначально Э. Дейкстрой, базируются именно на понятии семафора. Однако в случае систем реального времени применение семафоров для разделения доступа к ресурсу влечет потенциальную опасность возникновения инверсии приоритетов [4], избежать которой никак нельзя. Причина этого в том, что семафор по определению не может знать своего владельца (захватившего его), поскольку у счетного семафора в принципе не может быть владельца, а бинарный семафор, который отличает своего владельца (поток, заблокировавший в данный момент другие потоки на обращении к семафору), уже перестает быть собственно семафором и называется мьютексом, или эксклюзивной блокировкой. По-видимому, именно этим обстоятельством вызван тот факт, что все объекты синхронизации QNX, не реализованные на уровне native API, но предоставляемые на уровне API POSIX, строятся без использования семафоров. Принципиально схема работы семафора позволяет использовать его для решения максимально широкого круга задач. Приведем только несколько схематичных примеров: • Ожидание условия (уведомление). Часто возникает необходимость остановить (заблокировать) поток до тех пор, пока не наступит некое событие (выполнится условие). Разблокировать остановленный поток в таком случае может только другой, активный к этому времени поток. Предварительно инициализируем семафор нулевым значением:
В этом случае поток А ожидает выполнения некоего условия (операция sem_wait()), а поток В уведомляет (операция sem_post()) о выполнении условия любые ожидающие этого условия потоки.
Примечание — В принципе конструкция while() здесь не обязательна, можно обойтись и простым if(), но проверка выполнения условия и после разблокирования потока будет более строгой формой в многопоточной системе, особенно если выполнения условия ожидают более одного потока. • Взаимное исключение. С помощью семафора можно решить и другую классическую проблему синхронизации - безопасное совместное исполнение кода. Эта проблема возникает, когда из нескольких потоков необходимо провести модификацию общих переменных или обратиться к одному системному ресурсу. В таком случае необходимо установить четкий порядок обращений, не допускающий одновременности исполнения соответствующего участка кода (взаимное исключение). Здесь семафор инициализируется единицей:
Дата добавления: 2014-12-10; Просмотров: 773; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |