что такое общий семафор
Семафоры: введение
Семафоры
С емафор – это объект, который используется для контроля доступа нескольких потоков до общего ресурса. В общем случае это какая-то переменная, состояние которой изменяется каждым из потоков. Текущее состояние переменной определяет доступ к ресурсам.
Замечание: иногда мьютекс называют двоичным семафором, указывая не то, что мьютекс может находиться в двух состояниях. Но здесь есть важное отличие: разблокировать мьютекс может только тот поток, который его заблокировал, семафор же может «разблокировать» любой поток.
Семафоры описаны в библиотеке semaphore.h. Работа с семаформаи похожа на работу с мьютексами. Сначала необходимо инициализировать семафор с помощью функции
Далее, для ожидания доступа используется функция
Если значение семафора отрицательное, то вызывающий поток блокируется до тех пор, пока один из потоков не вызовет sem_post
Эта функция увеличивает значение семафора и разблокирует ожидающие потоки. Как обычно, в конце необходимо уничтожить семафор
Кроме тго, можно получить текущее значение семаформа
Здесь в переменную valp будет помещено значение счётчика.
Обратимся опять к старому примеру. Два потока изменяют одну переменную, копируя её в локальную переменную. Один поток увеличивает значение, а второй уменьшает 100 раз. Таким образом, в конце должен быть 0. Из-за совместного доступа к переменной получаем каждый раз разное значение
Синхронизируем теперь их с помощью семафора. Рассмотрим сначала пример, когда счётчик равен 1, значит, после того, как один из потоков уменьшит значение семафора, то второй вынужден будет ждать доступа к ресурсу (в данном случае, это тело функции worker1 или worker2 )
Здесь всё совершено понятно. Увеличим теперь значение счётчика до 3, например. Заменим
Мьютексы и семафоры
Рассмотрим аналогию: мьютекс можно сравнить с ключом от туалета на заправке. Если у вас есть ключ, то вы можете зайти в туалет, и туда больше никто не зайдёт. Если у вас нет ключа, то надо ждать, и в туалет вы зайти не можете. Мьютекс ограничивает доступ к общему ресурсу и позволяет в один момент времени пользоваться этим ресурсом только одному потоку.
Этот подход, к сожалению, не масштабируется на два туалета. И семафор не может разрешить проблему: по аналогии, мы имели бы на заправке два одинаковых ключа для двух одинаковых туалетов. Если один человек уже зашёл в туалет, то у второго оказался бы дубликат, способный открыть дверь туалета (свободного или занятого). Для предупреждения такой ситуации необходимо вводить флаг «туалет занят» (а это два мьютекса…), или же с самого начала использовать два разных ключа (т.е., два мьютекса). Семафор не помогает нам решать проблему доступа до набора идентичных ресурсов.
Цель использования семафора – оповещение одним потоком другого. Когда мы используем мьютекс, сначала мы его захватываем, потом используем ресурс, потом отдаём, и поступаем так всегда, потому что это обеспечивает защиту ресурса. В противоположность этому, потоки, которые используют семафор, либо ждут, либо сигналят, но не делают того и другого вместе. Например, один поток «нажимает» на кнопку, а второй отвечает на нажатие выводом сообщения.
Мьютекс:
//Поток 1
захватить_мьютекс(ключи_от_уборной)
использовать_ресурс
отдать_мьютекс(ключи_от_уборной)
//Поток 2
захватить_мьютекс(ключи_от_уборной)
использовать_ресурс
отдать_мьютекс(ключи_от_уборной)
Семафор
//Поток 1
послать_сигнал(состояние_кнопки)
Важно также, что сигнал может быть послан из обработчика прерываний, и не является блокирующей операцией, поэтому часто используется в ОС реального времени.
Рассмотрим следующий пример. Есть три потока. Все три создаются одновременно, но второй должен работать только после того, как отработает первый, а третий после того, как отработает второй.
2) Что такое семафор?
Что такое семафор?
Семафор — это просто переменная, которая неотрицательна и разделена между потоками. Семафор — это механизм сигнализации, и поток, ожидающий семафора, может быть сигнализирован другим потоком. Он использует две атомарные операции: 1) ожидание и 2) сигнал для синхронизации процесса.
Семафор разрешает или запрещает доступ к ресурсу, который зависит от того, как он настроен.
В этом руководстве по операционной системе (ОС) вы изучите:
Характеристика семафора
Здесь характерны семафоры:
Типы семафоров
Два распространенных вида семафоров
Подсчет семафоров
Этот тип семафора использует счетчик, который помогает заданию быть приобретенным или выпущенным много раз. Если начальный счетчик = 0, семафор счета должен быть создан в недоступном состоянии.
Однако, если счетчик> 0, семафор создается в доступном состоянии, и количество токенов в нем равно его количеству.
Бинарные семафоры
Бинарные семафоры очень похожи на подсчет семафоров, но их значение ограничено 0 и 1. В этом типе семафора операция ожидания работает только в том случае, если семафор = 1, а сигнальная операция завершается успешно, когда семафор = 0. Легко реализовать, чем считать семафоры.
Пример семафора
Приведенная ниже программа представляет собой пошаговую реализацию, которая включает использование и объявление семафора.
Ожидание и сигнальные операции в семафорах
Обе эти операции используются для реализации синхронизации процесса. Цель этой операции семафора — получить взаимное исключение.
Ждать операции
Этот тип операции семафора помогает вам контролировать ввод задачи в критическую секцию. Однако, если значение ожидания положительное, то значение аргумента ожидания X уменьшается. В случае отрицательного или нулевого значения, никакая операция не выполняется. Это также называется операцией P (S).
После уменьшения значения семафора, которое становится отрицательным, команда удерживается до тех пор, пока не будут выполнены требуемые условия.
Сигнальная операция
Этот тип операции семафора используется для управления выходом задачи из критического раздела. Это помогает увеличить значение аргумента на 1, который обозначается как V (S).
Подсчет семафора против двоичного семафора
Вот некоторые основные различия между счетным и двоичным семафором:
Считая семафор | Бинарный семафор |
Нет взаимного исключения | Взаимное исключение |
Любое целочисленное значение | Значение только 0 и 1 |
Более одного слота | Только один слот |
Предоставить набор процессов | У него есть механизм взаимного исключения. |
Разница между семафором и мьютексом
параметры | семафор | Mutex |
Механизм | Это тип сигнального механизма. | Это запирающий механизм. |
Тип данных | Семафор является целочисленной переменной. | Мутекс это просто объект. |
модификация | Операции ожидания и сигнала могут изменить семафор. | Он изменяется только процессом, который может запросить или освободить ресурс. |
Управление ресурсами | Если ни один ресурс не является свободным, то для процесса требуется ресурс, который должен выполнить операцию ожидания. Следует подождать, пока счетчик семафора не станет больше 0. | Если он заблокирован, процесс должен ждать. Процесс должен храниться в очереди. К этому нужно обращаться только тогда, когда мьютекс разблокирован. |
Нить | Вы можете иметь несколько программных потоков. | Вы можете иметь несколько программных потоков в мьютексе, но не одновременно. |
Владение | Значение может быть изменено любым процессом, освобождающим или получающим ресурс. | Блокировка объекта снимается только тем процессом, который получил блокировку для него. |
Типы | Типы семафора считаются семафором и двоичным семафором и | У Mutex нет подтипов. |
операция | Значение семафора изменяется с использованием операций wait () и signal (). | Объект мьютекса заблокирован или разблокирован. |
Ресурсы Занятость | Он занят, если все ресурсы используются, и процесс, запрашивающий ресурс, выполняет операцию wait () и блокируется, пока счетчик семафоров не станет> 1. | В случае, если объект уже заблокирован, процесс, запрашивающий ресурсы, ожидает и ставится в систему системой перед снятием блокировки. |
Преимущества семафоров
Вот плюсы / преимущества использования семафора:
Недостаток семафоров
Вот минусы / минусы семафора
Семафоры
Семафоры лучше всего предствлять себе как счетчики, управляющие доступом к общим ресурсам. Чаще всего они используются как блокирующий механизм, не позволяющий одному процессу захватить ресурс, пока этим ресурсом пользуется другой. Семафоры часто подаются как наиболее трудные для восприятия из всех трех видов IPC-объектов. Для полного понимания, что же такое семафор, мы их немного пообсуждаем, прежде чем переходить к системным вызовам и операционной теории.
Этот пример неплохо показал суть работы семафора, однако важно знать, что в IPC используются множества семафоров, а не отдельные экземпляры. Разумеется, множество может содержать и один семафор, как в нашем железнодорожном примере.
Машенька послала запрос на печать. Менеджер смотрит на семафоры и находит первый из них со значением 1. Перед тем, как Машенькин запрос попадет на физическое устройство, менеджер печати уменьшит соответствующий семафор на 1. Теперь значение семафора есть 0. В мире семафоров System V нуль означает стопроцентную занятость ресурса на семафоре. В нашем примере на принтере не будет ничего печататься, пока значение семафора не изменится.
Когда Машенька напечатала все свои плакаты, менеджер печати увеличивает семафор на 1. Теперь его значение вновь равно 1 и принтер может принимать задания снова.
Не смущайтесь тем, что все семафоры инициализируются единицей. Семафоры, трактуемые как счетчики ресурсов, могут изначально устанавливаться в любое натуральное число, не только в 0 или 1. Если бы наши принтеры умели печатать по 500 документов за раз, мы могли бы проинициализировать семафоры значением 500, уменьшая семафор на 1 при каждом поступающем задании и увеличивая после его завершения. Как вы увидите в следующей главе, семафоры имеют очень близкое отношение к разделяемым участкам памяти, играя роль сторожевой собаки, кусающей нескольких писателей в один и тот же сегмент памяти (имеется в виду машинная память).
Перед тем, как копаться в системных вызовах, коротко пробежимся по внутренним структурам данных, с которыми имеют дело семафоры.
Структура semid_ds ядра
Так же, как и для очередей сообщений, ядро отводит часть своего адресного пространства под структуру данных каждого множества семафоров. Структура определена в linux/sem.h:
Так же, как с очередями сообщений, операции с этой структурой проводятся с помощью системных вызовов, а не грязными скальпелями. Вот некоторые описания полей.
Это пример структуры ipc_perm, котораф описана в linux/ipc.h. Она содержит информацию о доступе к множеству семафоров, включая права доступа и информацию о создателе множества (uid и т.д.).
Время последней операции semop() (подробнее чуть позже).
Время последнего изменения структуры.
Указатель на первый семафор в массиве.
Число запросов undo в массиве (подробнее еще чуть позже).
Количество семафоров в массиве.
Структура sem ядра
В sem_ds есть указатель на базу массива семафоров. Каждый элемент массива имеет тип sem, который описан в linux/sem.h:
ID процесса, проделавшего последнюю операцию
Текущее значение семафора
Число процессов, ожидающих освобождения требуемых ресурсов
Число процессов, ожидающих освобождения всех ресурсов
Системный вызов semget() используется для того, чтобы создать новое множество семафоров или получить доступ к старому.
Создает множество семафоров, если его еще не было в системе.
При использовании вместе с IPC_CREAT вызывает ошибку, если семафор уже существует.
Аргумент nems определяет число семафоров, которых требуется породить в новом множестве. Это количество принтеров в нашей корпоративной комнате. Максимальное число семафоров определяется в
Аргумент nsems игнорируется, если вы открвываете существующую очередь.
Напишем функции-переходники для открытия и создания множества семафоров.
Системный вызов semop()
Аргумент sops указывает на массив типа sembuf. Эта структура описана в linux/sem.h следующим образом:
Номер семафора, с которым вы собираетесь иметь дело.
Выполняемая операция (положительное, отрицательное число или нуль).
Если sem_op положителен, то его значение добавляется к семафору. Это соответствует возвращению ресурсов множеству семафоров приложения. Ресурсы всегда нужно возвращать множеству семафоров, если они больше не используются!
Наконец, если sem_op равен нулю, то вызывающий процесс будет усыплен (sleep()), пока значение семафора не станет нулем. Это соответствует ожиданию того, что ресурсы будут использованы на 100%. Хорошим примером был бы демон, запущенный с суперпользовательскими правами, динамически регулирующий размеры множества семафоров, если оно достигло стопроцентного использования.
Чтобы пояснить вызов semop, вспомним нашу комнату с принтерами. Пусть мы имеем только один принтер, способный выполнять только одно задание за раз. Мы создаем множество семафоров из одного семафора (только один принтер) и устанавливаем его начальное значение в 1 (только одно задание за раз).
Каждый раз, посылая задание на принтер, нам нужно сначала убедиться, что он свободен. Мы делаем это, пытаясь получить от семафора единицу ресурса. Давайте заполним массив sembuf, необходимый для выполнения операции:
Третий аргумент (nsops) говорит, что мы выполняем только одну (1) операцию (есть только одна структура sembuf в нашем массиве операций). Аргумент sid является IPC идентификатором для нашего множества семафоров.
Когда задание на принтере выполнится, мы должны вернуть ресурсы обратно множеству семафоров, чтобы принтером могли пользоваться другие.
Трансляция вышеописанной инициализации структуры добавляет 1 к семафору номер 0 множества семафоров. Другими словами, одна единица ресурсов будет возвращена множеству семафоров.
Системный вызов semctl()
Вызов semctl используется для осуществления управления множеством семафоров. Этот вызов аналогичен вызову msgctl для очередей сообщений. Если вы сравните списки аргументов этих двух вызовов, то заметите, что они немного отличаются. Напомним, что семафоры введены скорее как множества, чем как отдельные объекты. С операциями над семафорами требуется посылать не только IPC-ключ, но и конкретный семафор из множества.
Оба системных вызова используют аргумент cmd для определения команды, которая будет выполнена над IPC-объектом. Оставшаяся разница заключается в последнем аргументе. В msgctl он представляет копию внутренней структуры данных ядра. Повторим, что мы используем эту структуру для получения внутренней информации об очереди сообщений либо для установки или изменения прав доступа и владения очередью. Для семафоров поддерживаются дополнительные команды, которые требуют данных более сложного типа в последнем аргументе. Использование объединения (union) огорчает многих новичков до состояния %(. Мы очень внимательно разберем эту структуру, чтобы не возникало никакой путаницы.
Аргумент cmd представляет собой команду, которая будет выполнена над множеством. Как вы можете заметить, здесь снова присутствуют IPC_STAT/IPC_SET вместе с кучей дополнительных команд, специфичных для множеств семафоров:
Берет структуру semid_ds для множества и запоминает ее по адресу аргумента buf в объединении semun.
Устанавливает значение элемента ipc_perm структуры semid_ds для множества.
Удаляет множество из ядра.
Используется для получения значений всех семафоров множества. Целые значения запоминаются в массиве элементов unsigned short, на который указывает член объединения array.
Выдает число процессов, ожидающих ресурсов в данный момент.
Возвращает PID процесса, выполнившего последний вызов semop.
Возвращает значение одного семафора из множества.
Возвращает число процессов, ожидающих стопроцентного освобождения ресурса.
Устанавливает значения семафоров множества, взятые из элемента array объединения.
Устанавливает значение конкретного семафора множества как элемент val объединения.
Аргумент arg вызова semсtl() является примером объединения semun, описанного в linux/sem.h следующим образом:
Определяет значение, в которое устанавливается семафор командой SETVAL.
Используется командами IPC_STAT/IPC_SET. Представляет копию внутренней структуры данных семафора, находящейся в ядре.
Указатель для команд GETALL/SETALL. Ссылается на массив целых, используемый для установки или получения всех значений семафоров в множестве.
Оставшиеся аргументы __buf и __pad предназначены для ядра и почти, а то и вовсе не нужны разработчику приложения. Эти два аргумента специфичны для LINUX-а, их нет в других системах UNIX-а.
Поскольку этот особенный системный вызов наиболее сложен для восприятия среди всех системных вызовов System V IPC, мы рассмотрим несколько его примеров в действии.
Следующий отрывок выдает значение указанного семафора. Последний аргумент (объединение) игнорируется, если используется команда GETVAL.
Возвращаясь к примеру с принтерами, допустим, что потребовалось определить статус всех пяти принтеров:
Программа пытается создать локальную копию внутренней структуры данных для множества семафоров, изменить права доступа и сIPC_SETить их обратно в ядро. Однако, первый вызов semctl-а немедленно вернет EFAULT или ошибочный адрес для последнего аргумента (объединения!). Кроме того, если бы мы не следили за ошибками для этого вызова, то заработали бы сбой памяти. Почему?
semtool: Интерактивное средство для работы с семафорами
Поведение semtool()-а зависит от аргументов командной строки, что удобно для вызова из скрипта shell-а. Позволяет делать все, что угодно, от создания и манипулирования до редактирования прав доступа и удаления множества семафоров. Может быть использовано для управления разделяемыми ресурсами через стандартные скрипты shell-а.
Синтаксис командной строки
Создание множества семафоров
semtool c (количество семафоров в множестве)
Запирание семафора
semtool l (номер семафора для запирания)
Отпирание семафора
semtool u (номер семафора для отпирания)
Изменение прав доступа (mode)
semtool m (mode)
Удаление множества семафоров
semtool d
Примеры semtool c 5
semtool l
semtool u
semtool m 660
semtool d
6.4.4. Разделяемая память
Разделяемая память может быть наилучшим образом описана как отображение участка (сегмента) памяти, которая будет разделена между более чем одним процессом. Это гораздо более быстрая форма IPC, потому что здесь нет никакого посредничества (т.е. каналов, очередей сообщений и т.п.). Вместо этого, информация отображается непосредственно из сегмента памяти в адресное пространство вызывающего процесса. Сегмент может быть создан одним процессом и впоследствии использован для чтения/записи любым количеством процессов.
Внутренние и пользовательские структуры данных
Давайте взглянем на структуру данных, поддерживаемую ядром, для разделяемых сегментов памяти.
Структура ядра shmid_ds
Так же, как для очередей сообщений и множеств семафоров, ядро поддерживает специальную внутреннюю структуру данных для каждого разделяемого сегмента памяти, который существует врутри его адресного пространства. Такая структура имеет тип shmid_ds и определена в Linux/shm.h как следующая:
Операции этой структуры исполняются посредством специального системного вызова и с ними не следует работать непосредственно. Вот описания полей, наиболее относящихся к делу:
Это образец структуры ipc_perm, который определен в Linux/ipc.h. Он содержит информацию о доступе к сегменту, включая права доступа и информацию о создателе сегмента (uid и т.п.).
Размеры сегмента (в байтах).
Время последней привязки к сегменту.
Время последней отвязки процесса от сегмента.
Время последнего изменения этой структуры (изменение mode и т.п.).
PID создавшего процесса.
PID последнего процесса обратившегося к сегменту.
Число процессов, привязанных к сегменту на данный момент.
Системный вызов shmget()
Чтобы создать новый разделяемый сегмент памяти или получить доступ к уже существующему, используется системный вызов shmget().
Этот новый вызов должен выглядеть для вас почти как старые новости. Он поразительно похож на соответствующие вызовы get для очередей сообщений и множеств семафоров.
Создает сегмент, если он еще не существует в ядре.
При использовании совместно с IPC_CREAT приводит к ошибке, если сегмент уже существует.
Повторимся, (необязательный) восьмеричный доступ может быть объеденен по ИЛИ в маску доступа.
Давайте создадим функцию-переходник для обнаружения или создания разделяемого сегмента памяти:
Как только процесс получает действующий идентификатор IPC для выделяемого сегмента, следующим шагом является привязка или размещение сегмента в адресном пространстве процесса.
Системный вызов shmat()
Если аргумент addr является нулем, ядро пытается найти нераспределенную область. Это рекомендуемый метод. Адрес может быть указан, но типично это используется только для облегчения работы аппаратного обеспечения или для разрешения конфликтов с другими приложениями. Флаг SHM_RND может быть OR-нут в аргумент флага, чтобы заставить переданный адрес выровняться по странице (округление до ближайшей страницы).
Кроме того, если устанавливается флаг SHM_RDONLY, то разделяемый сегмент памяти будет распределен, но помечен readonly.
Этот вызов, пожалуй, наиболее прост в использовании. Рассмотрим функцию-переходник, которая по корректному идентификатору сегмента возвращает адрес привязки сегмента:
Если сегмент был правильно привязан, и процесс имеет указатель на начало сегмента, чтение и запись в сегмент становятся настолько же легкими,как манипуляции с указателями. Не потеряйте полученное значение указателя! Иначе у вас не будет способа найти базу (начало) сегмента.
Вызов очень похож на msgctl(), выполняющий подобные задачи для очередей сообщений. Поэтому мы не будем слишком детально его обсуждать. Употребляемые значения команд следующие:
Берет структуру shmid_ds для сегмента и сохрает ее по адресу, указанному buf-ом.
Устанавливает значение ipc_perm-элемента структуры shmid_ds. Сами величины берет из аргумента buf.
Помечает сегмент для удаления.
IPC_RMID в действительности не удаляет сегмент из ядра, а только помечает для удаления. Настоящее же удаление не происходит, пока последний процесс, привязанный к сегменту, не «отвяжется» от него как следует. Конечно, если ни один процесс не привязан к сегменту на данный момент, удаление осуществляется немедленно.
Снятие привязки производит системный вызов shmdt.
После того, как разделяемый сегмент памяти больше не нужен процессу, он должен быть отсоединен вызовом shmdt(). Как уже отмечалось, это не то же самое, что удаление сегмента из ядра. После успешного отсоединения значение элемента shm_nattch структуры shmid_ds уменьшается на 1. Когда оно достигает 0, ядро физически удаляет сегмент.
shmtool: Интерактивный обработчик разделяемых сегментов памяти
Синтаксис командной строки
Запись строк в сегмент
Получение строк из сегмента
Изменение прав доступа (mode)
shmtool w test
shmtool w «This is a test»
shmtool r
shmtool d
shmtool m 660
семафор
Смотреть что такое «семафор» в других словарях:
СЕМАФОР — (греч., от sema знак, и phoros несущий). 1) высокие столбы близ железнодорожных станций с подвижными крыльями для дневных сигналов и с фонарями для ночных. 2) морские телеграфы для подачи сигналов с берега подходящим судам, помощью флагов и… … Словарь иностранных слов русского языка
семафор — а, м. sémaphore m. <гр. sema знак + phoros несущий. 1. На железных дорогах высокие столбы с подвижными поворотными крылами, установленными вблизи станции, для указания машинистам проходящих поездов о возможности или невозможности дальнейшего… … Исторический словарь галлицизмов русского языка
Семафор — (фр. Sémaphore, от греч. σήμα знак, сигнал и φορός несущий): Семафор (железная дорога) устройство для регулирования железнодорожного движения. Семафор способ визуальной связи между кораблями с использованием жестов. См. Русская семафорная… … Википедия
СЕМАФОР — (от греч. sema знак сигнал и phoros несущий), сигнальное устройство.1) Семафор железнодорожный мачта с подвижными крыльями; сигналом является определенное взаимное расположение крыльев. Применяется на железных дорогах без автоблокировки.2)… … Большой Энциклопедический словарь
СЕМАФОР — СЕМАФОР, семафора, муж. (от греч. sema знак и phoros несущий). Сигнальный столб на железнодорожных путях, указывающий машинисту, закрыт или открыт путь для движения поезда (ж. д.). Открыть, закрыть семафор. Поезд остановился, потому что семафор… … Толковый словарь Ушакова
Семафор — специальный тип данных в некоторых языках программирования. Семафор управляет доступом к вычислительным ресурсам, к которым стремятся разные процессы. См. также: Типы данных Параллельная обработка Финансовый словарь Финам … Финансовый словарь
семафор — лицо, сигнализатор, светофор Словарь русских синонимов. семафор сущ., кол во синонимов: 7 • лицо (135) • светило … Словарь синонимов
семафор — Переменная, используемая для обеспечения взаимного исключения. [ГОСТ 19781 90] семафор Способ синхронизации задач и управления доступом к общему ресурсу. [http://www.morepc.ru/dict/] Тематики обеспеч. систем обраб. информ. программное EN… … Справочник технического переводчика
СЕМАФОР — (от греческого sema знак, сигнал и phoros несущий), устройство для подачи разрешающих и запрещающих движение сигналов поездам (устанавливается на железных дорогах, не оборудованных автоматической блокировкой движения, в виде мачты с подвижными… … Современная энциклопедия
СЕМАФОР — СЕМАФОР, устройство, зрительно передающее сообщения; разновидность оптического ТЕЛЕГРАФА. Железнодорожные семафоры представляют собой вертикальную мачту с подвижным плечом, которая образует угол, указывающий «свободно» или «опасность». Семафором… … Научно-технический энциклопедический словарь
СЕМАФОР — СЕМАФОР, а, муж. 1. Сигнальное устройство в виде подвижных крыльев или (у морских, речных семафоров) рея на столбе, мачте. Железнодорожный с. Речной с. С. открыт (путь свободен). 2. Во флоте: способ зрительной сигнализации (флажками, руками или… … Толковый словарь Ожегова