Программирование: введение в профессию. III: системы и сети

image of the cover

Аннотация

В третий том книги «Программирование: введение в профессию» вошли части V–VIII.

В части V рассматриваются системные вызовы для ввода-вывода, управление процессами, механизмы взаимодействия процессов, такие как сигналы и каналы, а также понятие терминала и связанные с ним явления, в том числе сеансы и группы процессов, виртуальные терминалы, управление дисциплиной линии.

Часть VI посвящена компьютерным сетям; даётся небольшой обзор протоколов, используемых в сети Интернет, рассмотрена подсистема сокетов и событийно-ориентированное построение серверных программ.

В части VII рассматриваются вопросы, связанные с разделяемыми данными, критические секции, взаимоисключение; даются базовые сведения о библиотеке pthread.

Часть VIII содержит ряд сведений о внутреннем устройстве операционной системы; в частности, рассматриваются различные модели виртуальной памяти, подсистема ввода-вывода и т.п.

Публикация в бумажном варианте

Опубликовано издательством МАКС Пресс (Москва) в 2017 году. ISBN 978-5-317-05606-3.

Электронная версия

Электронная версия, идентичная печатному изданию, доступна здесь: http://www.stolyarov.info/books/pdf/progintro_vol3.pdf

Статус бумажной версии

В настоящее время может быть приобретена в здании факультета ВМК, а также заказана с доставкой на этом сайте.

Архив примеров программ

Архив, содержащий примеры программ из первых трёх томов, можно скачать здесь: http://www.stolyarov.info/books/extra/progintro_vol123_examples.tgz

Напоминаем, что раскрыть этот архив можно командой

   tar -xzf progintro_vol123_examples.tgz

Здравствуйте. У

Здравствуйте. У меня проблема с запуском X-сервера и xterm в нём. На 28-й странице у вас об этом написано.
Суть в том, что, когда я запускаю X-сервер на дисплее 1, то он явно запускается, но никакого фона и курсора нет. Просто чёрный экран. Когда я нажимаю Ctrl-Alt-F1, ввожу DISPLAY=:1 xterm, а затем нажимаю Alt-F7, переключение происходит на дисплей 0, а дисплей 1 снова открывается только если я убью процесс с этим xterm-ом. В чём может быть дело?

admin аватар

Что-то у меня

Что-то у меня ощущение, что вы путаете "дисплеи" в смысле X Window и виртуальные консоли, у которых нумерация своя.

В принципе любой X-сервер может быть запущен на любой виртуальной консоли и при этом иметь любой номер дисплея, т.е. номер вирт.консоли никак не связан с номером дисплея. На 7-й консоли у вас, видимо, штатный X-сервер, запускаемый вашим дистрибутивом линукса или что у вас там. Если так, то, скорее всего, тот X-сервер, который запускаете вы (вручную, как 1-й дисплей) берёт себе 8-ю консоль, так что переключаться на него (с любой из текстовых консолей) следует по Alt-F8. Непонятно только, почему это не происходит само собой. Но вообще это всё напоминает "диагноз по фотографии", сложно сказать, что там у вас такое происходит, не видя машины.

Андрей

Андрей Викторович, на странице 176 - "маршрутизация для Дмитрия" - случайно, не пропущен маршрут для связи с "12-й сеткой", что-то вроде 192.168.12.0/24 -> 192.168.14.1? Или это я туплю )))

admin аватар

А зачем? Там уже

А зачем? Там уже есть 0.0.0.0/0 -> 192.168.14.1, "12я сетка" ни фига не особенная :-)

стр 219

Здравствуйте, Андрей Викторович.
На странице 219 в листинге кода отсутствует вызов listen, чтобы сделать сокет слушающим.

admin аватар

В новом издании

В новом издании уже исправлено.

Здравствуйте,

Здравствуйте, стр. 84.

> приводят к системному вызову _exit

Здесь и до этого так же упоминался "системный вызов" _exit. Но судя по открытым исходникам линукса, системный вызов всё-таки называется просто exit, ну или exit_group как указано в сноске, а _exit - это обертка из стандартной библиотеки.

admin аватар

Кончайте

Кончайте бредить. exit без подчёркивания — это (документированно!) библиотечная функция, которая, прежде чем завершить программу (то есть обратиться к ядру системы), вызывает ещё все хуки, которые были навешаны на неё функцией atexit (читайте man 3 atexit). Этим пользуется подсистема "стандартного" ввода-вывода, чтобы вытеснить буфера.

_exit с подчёркиванием — это официальное название системного вызова, который, если интересно, в Linux/i386 и FreeBSD/i386 имел номер 1.

К сожалению (именно к сожалению — я бы сказал, к величайшему, поскольку это одно из свидетельств безнадёжной деградации IT-индустрии) "современные" версии "стандартной" библиотеки, поддерживающие (читай -- заточенные) на мультитрединг (относительно которого я говорил, говорю и буду говорить, что применять его недопустимо -- никогда и ни для чего), предпочитают завершать процесс вызовом exit_group, для чего функция с названием _exit там представляет собой не обёртку для соответствующего системного вызова, а простую функцию, которая — ага — вызывает exit_group. Факта существования системного вызова _exit это никак не отменяет. Аналогичная ситуация была, например, с вызовом signal: ядро такой вызов поддерживало, но одноимённая библиотечная функция вместо "своего" вызова обращалась к sigaction. Вот вам, кстати, цитата из man 2 _exit:

In  glibc  up  to version 2.3, the _exit() wrapper function invoked the
kernel system call of the same name.   Since  glibc  2.3,  the  wrapper
function  invokes  exit_group(2),  in  order  to  terminate  all of the
threads in a process.

NB: если НЕ использовать мультитрединг (а я всё-таки надеюсь, что мои читатели не станут пополнять ряды безмозглых макак, которые его применяют), семантика библиотечной функции _exit ничем не отличается от семантики классического системного вызова _exit.

Текущий каталог процесса-демона

Андрей Викторович, здравствуйте, прошу разъяснить небольшой момент. На странице 146 вначале сказано что "При старте демона принимаются меры, чтобы его функционирование не мешало работе и администрированию системы.Так, текущий каталог обычно меняется на корневой, чтобы не мешать системному администратору при необходимости удалять каталоги, монтировать и отмонтировать диски и т.п." Этот момент остался мне не понятен, прошу пояснить каким образом текущий рабочий каталог процесса-демона может помешать системному администратору?

admin аватар

Попробуйте

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

Hint: будет ошибка "Device busy". Потому что нельзя отмонтировать файловую систему, внутри которой кто-то сидит, то есть в системе существует хотя бы один процесс, текущий каталог которого расположен внутри этой файловой системы.

Между прочим, это знает каждый, у кого основным инструментом работы с компьютером является командная строка. Это просто невозможно не знать, такая ситуация случается едва ли не каждый день в ходе обычной работы.

В предисловиях к моим книгам чётко сказано, что переход на *nix и командную строку — это обязательное условие для того, чтобы от книг был хоть какой-то толк. Не хотите переходить на *nix и командную строку — дело ваше, но хотя бы не тратьте время на мои книги, толку не будет всё равно.

Спасибо

Спасибо за подробный ответ! Теперь всё стало ясно.

admin аватар

Ну, вам-то может

Ну, вам-то может и стало ясно, зачем демоны chdir делают, а мне вот по-прежнему неясно, что вы на моём сайте забыли.

Опечатка

Здравствуйте, Андрей Викторович.
стр. 119 "при этом, ни с каким файлами они не связаны"

admin аватар

В тексте книги

В тексте книги нет запятой после "при этом", в вашей цитате есть. Если вы считаете, что "опечатка" состоит в этом, то заблуждаетесь, поскольку запятая здесь не нужна.

какимИ

"ни с какими файлами они не связаны"
Возможно, имелось ввиду отсутствие буквы "и" в слове "какими".

admin аватар

Факт.

Да, спасибо. Я это ухитрился не заметить, глядя прямо на эту строчку.

Здравствуйте.

Здравствуйте. стр. 84.

> в составе выражения exit(main(0)).

Почему именно обертка exit? На ассемблере мы вызывали системный вызов _exit, правильнее ли было указать здесь _exit(main(0))?

admin аватар

Нет, разумеется

Именно библиотечная функция exit. На досуге попробуйте вывод программы hello, world перенаправить в файл и осознайте, что если бы там был _exit, ваш файл остался бы пустым.

NB: нуля там никакого нет, откуда вы его взяли? Там просто exit(main()).

С нулем ошибся,

С нулем ошибся, да.

Исходя из exit(main()) - если внутри main мы используем exit(0) вместо return 0. Тогда с каким значением будет вызвана внешняя exit, если во время вызова внутреннего exit буфера очистятся и будет вызван системный вызов _exit, который завершит процесс?

admin аватар

Если внутри main,

Если внутри main, а равно и в любом другом месте программы мы вызовем exit(хоть от нуля, хоть от чёрта лысого), то больше уже ничего вызвано не будет. Библиотечная версия exit отработает хуки, которые на неё понавешаны с помощью atexit (в большинстве программ это только вытеснение буферов высокоуровневого вывода, больше ничего), после чего сделает _exit, и на этом всё кончится. До этой вашей "внешней exit" управление не дойдёт.

Мне вот интересно, что у вас за каша в голове, если такие вопросы возникают. К сожалению, через комменты я это вряд ли определю, но что каша имеет место — это вообще без вариантов.

> До этой вашей

> До этой вашей "внешней exit" управление не дойдёт.

Возвращаемся к выражению exit(main()). Если до "внешней exit" управление не дойдет, значит выражение exit(main()) само по себе не является обобщенным вариантом? Оно актуально только если main заканчивается через return и тогда управление таки дойдет до "внешней exit"?

Каши никакой нет, все достаточно подробно и грамотно расписано, чтобы она не появилась. Но, тем не менее, есть некоторые моменты, которые вызывают вопросы. При изучении любого нового материала может не до конца быть все понятно, обратная связь на то и нужна.

Так что отдельная благодарность за этот сайт.

admin аватар

Простите, а что

Простите, а что такое "является обобщённым вариантом"? Или "не является обобщённым вариантом"? И вообще, что такое "обобщённый вариант"?

По существу вопроса: ну вот есть у вас, к примеру, две функции в программе, одна называется f, другая g, вы пытаетесь выполнить f(g()), только одна неприятность — g берёт, да и рушит всю программу. Ну, в смысле, завершает. Мне вот что интересно, вы понимаете, что в такой ситуации бессмысленно спрашивать, с каким значением параметра будет вызвана f, поскольку она уже не будет вызвана?

А каша в мозгу у вас есть, можете не сомневаться. Иначе мне бы не приходилось сейчас ломать голову над тем, откуда у вас выполз этот "обобщённый вариант" и что бы он такое значил. Вы, случайно, первый том и паскалевскую часть не пропустили? Обычно те, кто уже программировал, таких вопросов не задают.

Нет, первый том

Нет, первый том я прочитал.

> вы понимаете, что в такой ситуации бессмысленно спрашивать, с каким значением параметра будет вызвана f, поскольку она уже не будет вызвана?

Именно, это само собой разумеется. Просто вот эта аналогия, когда мы выполняем f(g()), а функция g завершает программу,
должна быть тоже как-то отражена в строках книги. А то в выражении exit(main()) невольно подумаешь, что даже если main завершит преждевременно программу, exit все равно выполнится с кодом от main.

Это лишь уточнение. Возможно, его стоило бы включить в книгу. Может для кого-нибудь это тоже будет не столь очевидно, хотя как знать, воля Ваша.

> И вообще, что такое "обобщённый вариант"

Поясню, я имел в виду, что запись exit(main()) означает всегда сначала вызывается main, а потом в любом случае будет вызвана exit с кодом от main. Но, как мы разобрали, не всегда. Это я и хотел уточнить.

admin аватар

Да прочитать-то

Да прочитать-то вы его, может, и прочитали, а на Паскале вы хоть что-нибудь полезное сделали? Паскаль же там излагается не для того, чтоб про него просто "прочитать", нужно наработать опыт работы на нём, иначе про него и рассказывать бесполезно.

А вот про функции — ну вот не вижу я логики в ваших словах. Совсем. Как что-то может быть вызвано "в любом случае", когда программа уже кончилась? Ну всё уже, нету её, завершилась, схлопнулась, больше не выполняется, как в ней что-то может быть ещё вызвано?

Простите, а как

Простите, а как написание чего-то полезного на Паскале поможет при уточнении данного вопроса?

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

Паскаль

от опыта работы на Паскале в данном вопросе ни горячо, ни холодно.

Почему? Опыт же приобретаете. На Паскале можно писать хороший качественный софт в отличие от языков скриптовых поделок с помощью которых "обучают" на говнокурсах. То, что он "непопулярен", ни о чём не говорит. И не смотрите на все эти "рейтинги популярности ЯП". Они составляются фиг пойми кем и представляют собой лютую дичь, представляющую собой кучу совершенно разных ЯП, которые даже и сравнивать по-хорошему нельзя.

admin аватар

Если бы у вас

Если бы у вас был опыт работы на Паскале, ещё раз повторяю, вы бы таких вопросов не задавали, поскольку чувствовали бы на уровне интуиции, как именно происходит работа программы и что собой представляет её досрочное завершение. И, подчеркну, далеко не только это. Паскалю в моём курсе посвящена одна из самых крупных частей, и это отнюдь не для мебели.

Вообще говоря, если вы считаете мою методику обучения неправильной, то это ваше право, но мне не вполне понятно, что вы такого забыли на моём сайте. Есть другие сайты и другие преподаватели, которые с удовольствием помогут вам в стремлении пополнить ряды безмозглых обезьянок, почему-то именующих себя программистами.

1. Ваш цикл я

1. Ваш цикл я нашел по рекомендациям и сходу же прямо с оглавления понравился именно подход, когда только Unix и стремление научить низкоуровневым вещам.

2. Книга по Паскалю очень классная. Намного лучше того, что мне преподавали когда-то в университете. То же самое касается и ассемблера с Си. Одна только глава про написание программы без использования стандартной библиотеки чего стоила.

3. Я еще не до конца дочитал про ОС, но даже то, что отдельная глава есть для параллельного программирования, уже выше всех похвал наряду с теми, что мне давали в университете.

4. Но самое главное, что сейчас это все доступно в бесплатном варианте и с живой поддержкой в виде как раз этого сайта.

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

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

admin аватар

Короче говоря,

Короче говоря, мой вердикт как преподавателя тут такой. Если хотите чему-то научиться — берите в руки Free Pascal и пишите какую-нибудь текстовую (но при этом полноэкранную) игрушку. Желательно не такую, какая уже есть, а собственного изобретения. Когда найдётся кто-нибудь, кто добровольно (а не потому что вы его слёзно умоляли) на вашу игрушку потратит хотя бы минут десять — считайте, что тему Паскаля вы для себя раскрыли.

Желательно потом ещё и на ассемблере что-то подобное сотворить, но там я, конечно, не ожидаю, что вы что-то полноэкранное изобразите. Ну хоть что-то, какую-то сколько-нибудь полезную программу сделать крайне желательно: это ваш первый и последний шанс обрести опыт на асме, потом (в течение всей жизни, увы) на это никогда не будет времени, поверьте моему опыту.

И вот только после этого возвращайтесь к изучению Си. Тогда (и не раньше) вы будете к этому готовы.

NB: последовать этому совету или нет — дело ваше. Но если решите, что нет — то хотя бы не тратьте время на мои книжки, толку всё равно не будет.

UPD: Я вынужден подчеркнуть ещё раз: я не считаю, что вы готовы к освоению Си, и отвечать на ваши вопросы по Си более не буду. Ваш очередной комментарий раскрыт не будет. NB: из текста вашей очередной пачки "вопросов" видно, что в мозгах у вас не просто каша, там просто треш и угар. Что делать — я вам уже сказал. Хотите — делайте, не хотите — не делайте, но ищите тогда других учителей.

Спасибо за

Спасибо за советы.

Здравствуйте.

Здравствуйте. Возможные опечатки на страницах 60-61.

> процесс, выполняющий вызов, должен иметь euid, равный 0
> указанный третьим параметром gid должен либо быть равен euid'у

В первом случае должен быть uid.
Во втором - gid.
И в слове "параметром" буква пропущена.

admin аватар

В первом случае

В первом случае должен быть именно euid -- effective uid, именно он определяет полномочия.

Во втором должен быть egid, а не euid, как там написано; это уже известная опечатка.

А вот про "парметром" -- спасибо большое, и корректор это прощёлкал, и сам я проглядел, хотя совсем недавно редактировал этот кусочек.

Здравствуйте,

Здравствуйте, страница 39.

> уже на основе информации из /etc/groups

Разве не /etc/group?

admin аватар

Абсолютно

Абсолютно верно, спасибо! Вовремя.

Остановился на

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

admin аватар

Книга написана

Книга написана в предположении, что читателю этот материал интересен. В этом случае любой предложенный инструмент вызывает мысли вроде "а теперь я, наверное, могу вот это и вот то", всё тут же хочется попробовать, и задачи для этого придумываются сами собой. Во всяком случае, когда я программировать учился, со мной происходило именно так, и всевозможные задания только мешали, хотелось делать исключительно то, что хочется самому.

Судя по тому, сколь настойчиво публика требует от меня задачник, так получается не у всех. В общем и целом, задачник стоит в планах, я даже начал его писать, но сейчас всё-таки сначала хочется добить второе издание.

Добрый

Добрый день!

Меня смущает пример в спящем парикмахере с размещением up(customers) после критической секции в функции клиента (последний абзац стр. 287). Смущение, возможно, наличествует из-за недостатка понимания, но я, тем не менее, его тут обозначу.

Следуя схожей логике можно вообразить, что в при правильной реализации одновременно заходят два клиента, один проходит критическую секцию и откачивается на диск не встав на блокировку перед lock(barber). В это же время на seat_mutex заблокированы второй клиент и уже проснувшийся брадобрей. Допустим первым разблокируется второй клиент и с ним, вот незадача, происходит то же самое, что с первым - он откачивается на диск. Дальше брадобрей уже наконец попадает в критическую секцию (уже свою, но тоже по seat_mutex) и так же, как в примере неправильно реализации, дважды вхолостую делает работу до того, как оба клиента сделают CUSTOMER_WORK.

Вы могли бы мне указать, где я не прав?

admin аватар

ох уж этот парикмахер

Это вот, случайно, не та же самая проблема?

http://www.stolyarov.info/guestbook/archive/3#comment-2797

парикмахер - тот ещё перец

Как минимум из той же оперы.
Спасибо, буду теперь иметь в виду возможность заглянуть в гостевую книгу - в конце-концов, не один же я такой непонятливый!

ЗЫ: не по теме, но о наболевшем - капчи у Вас тут зубодробительные!

admin аватар

Про капчу --

Про капчу -- если зарегистрироваться, то залогиненным пользователям капчу не показывают. Один раз при регистрации преодолеете -- и всё.

7.4.2 Создание дополнительного файла

На стр.302 предлагается создать файл foobar.dat.lock с использованием флага O_EXCL, а в листинге программы на стр.303 в системном вызове open этот флаг не используется (вместо него используется O_CREAT).

admin аватар

Есть такое, уже

Есть такое, уже поймали. Спасибо за внимательность.

Семафоры Дейкстры

На стр.264 есть противоречие в следующем предложении:
"Операция down должна, наоборот, уменьшать значение на 1, но сделать это она может только когда текущее значение строго больше нуля, ведь значение семафора не может быть отрицательным."
А как же ноль?

admin аватар

Ну так если

Ну так если семафор уже равен нулю, его уменьшить дальше нельзя — в минус уйдёт, а ему запрещено. Там в тексте это вроде написано всё, в этом случае down приводит к блокировке (ради неё, собственно, семафоры и придуманы).

Получаю

Получаю странные результаты работы программы prod_cons. Программа была запущенна 100 раз в цикле c двумя текстовыми файлами на входе.

Содержимое первого файла:
3.14 0.234234234 12341.2323525

Содержимое второго файла:
1241241.11112444 234234.22344 34345345 345345

В 93 из 100 случаев результат был следующий:
total average: 9.373352 (sum = 65.613466; count = 7)

В остальных же:
total average: 10.744874 (sum = 64.469243; count = 6)
total average: 8.810194 (sum = 52.861167; count = 6)
total average: 8.596974 (sum = 51.581843; count = 6)
total average: 8.596974 (sum = 51.581843; count = 6)
total average: 8.810194 (sum = 52.861167; count = 6)
total average: 10.744874 (sum = 64.469243; count = 6)
total average: 8.596974 (sum = 51.581843; count = 6)

admin аватар

В общем даже

В общем даже понятно, почему. Программа завершается, если видит, что все производители завершились. При этом никто не проверяет, что буфер опустел, и тем более никто не проверяет, нет ли у консьюмеров ещё чего-то "недоскинутого".

Вообще, конечно, вот прямо классический race condition. Надо будет подумать, что с этим можно сделать.

UPD: Всё оказалось ещё проще. В той версии, которая в книжке, консьюмер, если не может сразу заполучить grand_mutex, плюёт на это дело и оставляет информацию в своих локальных переменных. Если он сюда придёт ещё раз, то всё будет хорошо (ибо он тогда всю инфу сразу сольёт), вот только он же может больше сюда и не прийти -- числа кончатся, он повиснет на sem_wait(&buf_full), потом процесс завершится вместе со всеми тредами.

Что называется, хотел один такой (то есть я) продемонстрировать неблокирующий вариант захвата мьютекса, а получилась в итоге полная хрень. Это легко скорректировать, если при наличии локальной инфы опускание семафора тоже сделать неблокирующим, типа, если в основную секцию не пустили, а у меня есть что скинуть, то я хотя бы в секцию по grand_total попробую снова пойти. Но наглядность текста примера после этого упадёт резко, то есть там будет вообще непонятно, что происходит. Так что, видимо, придётся try_lock выкинуть из примера.

Спасибо за

Спасибо за пояснение и неожиданный пример race condition :)

На страницах

На страницах 262-263 нет никакой путаницы с числами 0 и 1, представляющими открытый и закрытый мьютекс? В самом верхнем сниппете кода на 263 стр lock и unlock должны будут работать как задумано только если 0 - "открыто", 1 - "закрыто". Сейчас, например, получается, что unlock закрывает мьютекс (а не открывает), присваивая ему 0, потому что, как на предыдущей странице было сказано, 0 соответствует состоянию закрытого мьютекса

admin аватар

Ухххххх!

Есть, конечно. Ещё как есть. Сейчас посмотрел — стало грустно.

Спасибо за внимательность — на эту ахинею до вас никто внимания не обратил, так что она чуть было не проползла во второе издание.

Настройки просмотра комментариев

Выберите нужный метод показа комментариев и нажмите "Сохранить установки".