Реферат: Процессы и IPC
}
sub Error
{
my ($error_text) = @_;
select($oldout);
kill KILL => $fpid;
close(FOR_WRITE);
print "Error: $error_text\n";
exit;
}
Ну что, налили очередную порцию кофе? Тогда приступим к разбору полетов. Первым делом, программа связывает два дескриптора FOR_READ и FOR_WRITE в канал с помощью pipe. При этом, FOR_READ получится только для чтения, а FOR_WRITE, соответственно, только для записи. Про следующие две строки скажу только что они отключают буфферизацию. Для нас это пока не важно, их можно вообще убрать. Далее вызывается функция start_filter(). Вот на нее нужно взглянуть со всей внимательностью.
Первым делом, функция создает дубликат процесса. В родительском процессе закрывается ненужный дескриптор для чтения, а в качестве STDOUT подсовывается один конец созданного канала, тот который только для записи. После этого программа возвращается к своему основному бесполезному занятию, а точнее – эмулирует вывод. После того, как вывод завершен, не забываем закрыть дескриптор вывода, иначе все повиснет по причине вышеописанной. Далее ждем, когда дочерний поток завершится (можете закомментировать эту строку, и посмотреть что получится). На этом работа основного процесса завершена.
Вернемся к функции start_filter() в той его части, где выполняется непосредственно фильтрация. Первым делом неиспользуемый в дочернем процессе конец канала закрывается. Далее, процесс в цикле считывает данные из канала и конкатенирует их в переменной $out. Ну далее должно быть все понятно. Запустите программу. Работает? По крайней мере должна.
Теперь уберите комментарии из строки, где вызывается функция Error("bug"). Запустите программу снова. Ну, каков результат? Этого мы добивались? (Правильный ответ да, если нет, то смотрите что вы там понаписали).
Давайте посмотрим, что делает функция Error(). Первым делом восстанавливает стандартный поток вывода. В это время дочерний процесс в режиме накопления данных ничего об этом не подозревает. Следующая жестокая операция убивает дочерний процесс. А дочерний процесс еще ничего не вывел в поток вывода. После закрывается дескриптор для записи, и выполняется обработка ошибки. Ну и что бы миновать последние строки головного процесса выполняется exit.
Вот и все. Как уж вы будете применять полученную информацию на практике, дело ваше.
Что такое переменная
Переменные появились вместе с первыми языками программирования. Результат работы любой программы сводится к манипуляциям над какими-нибудь данными. Вы наверное уже знаете, что память - это последовательность ячеек, каждая из которых представляет собой числовое значение от 0 до 255 - 1 байт. Так как ячеек очень много, единственный вариант как то ориентироваться - это пронумеровать каждую ячейку. Так и есть - каждый байт оперативной памяти доступен процессору посредством порядкового номера ячейки - адреса.
В то время, когда программы писались в машинных кодах, программист должен был помнить в какую ячейку памяти он записал нужное значение. Представьте, как усложнялся процесс написания программы, когда возникала необходимость работать с несколькими значениями. Адреса ячеек являются простыми числами, которые мало о чем могут рассказать человеку. Понятно, что раз они не несут никакой слысловой нагрузки, программисту было очень трудно ориентироваться при больших объемах памяти.
С появлением первых языков программировани появилась очень полезная возможность ассоциировать с определенным адресом символьные имена. В отличии от адреса, имя переменной представляется некоторой лексемой, в достаточной мере отражающей содержимое конкретной ячейки памяти. Однако, имя это не все, что характеризует переменную. Так как процессор может работать с тремя типами данных (байт, слово и двойное слово), определение переменной в языках низкого и среднего уровней как правило сопровождается указанием типа переменной. Эти два параметра однозначно определяют расположение ячейки, и способ использования этой ячейки. Определим переменную типа байт (BYTE) с именем А. Естественно, что процессор, зная что переменная, находящаяся по такому адресу соответствует типу BYTE, при извлечении значения этой ячейки выберет такую команду, которая извлекает байт, а не слово или двойное слово.
В общем случае, переменная - это поименованная область памяти. В зависимости от языка, объявление переменной может сопровождаться указанием типа. Обычно, в языках высокого уровня указание типа не используется. Как правило, синтаксические различия между языками чаще всего проявляются именно в способах объявления переменных (C,C++ - конкретизация типа; perl - идентификатор структуры $,@ или %).
Однако, в современном мире программирования имя и тип это еще не все, что программист должен знать о переменной. Существуют такие понятия как пространство имен и область действия. Представьте, что вы пишете программу, в которой используются несколько переменных. Имена все переменных составляют список, определяющий пространство имен. Предположим, в процессе разработки вы допустили ошибку, и объявили две переменные с одинаковыми именами. При попытке собрать программу ваш компилятор предупредит об этой ошибке, что было бы невозможно, если бы имена всех переменных оставались без контроля со стороны компилятора. Таким образом, целостность пространства имен сваливается с плеч программиста на компилятор, что опять же, значительно облегчает процесс разработки и отладки. В действительности, приведенный пример не является ошибочным для всех языков программирования. Каждый компилятор (или интерпретатор) выдвигает свои требования к пространству имен и что в одном языке (в данном случае C и C++) является ошибкой, в других языках может таковой и не быть.
Если раньше программы были достаточно маленькими, что бы уместиться в одном файле, то сейчас исходный код программ может состоять из нескольких файлов. При этом абсолютно нереально запомнить уникальные имена по всей программе. В связи с этим (и не только) было введено понятие области действия (или области видимости) переменной. Область действия понятие абстрактное. Этот термин применителен только по отношению к языкам среднего и высшего уровней. Смысл в том, что бы как-то разбить пространство имен на несколько независимых частей. Таким образом, в одной программе смогут безболезненно сосуществовать несколько переменных с одинаковыми именами и типами. У опытных программистов область действия ассоциируется с текущим программным блоком, но так как мы еще не знаем, что такое программный блок, мы рассмотрим это на другом примере. Новичкам будет более понятна концепция разделения области действия в пределах разных файлов (такая методика используется, например, в perl). Это значит, что переменная объявленная в одном из файлов программы будет абсолютно не видна из других файлов программы (при условии, что мы объявим переменную с помощью спецификатора my). Таким образом, мы можем объявить переменную с именем Variable в нескольких файлах проекта и это не будет ошибкой. Более подробно области действия мы будем рассматривать в процессе разбора программных блоков.
Что такое тип данных
Тип данных определяет, что из себя представляет значение и позволяет узнать, каким образом его можно(нужно) обработать. Типы подразделяются на две категории: базовые и пользовательские.
Первая категория - это встроенные типы значений, которые известны компилятору (или интерпретатору). Это значит, что компилятор (интерпретатор) знает каким образом нужно обрабатывать те или иные значения и какие операции можно выполнять с этими значениями, а какие нельзя. Набор базовых типов специфичен для разных компиляторов. Иначе говоря, программе написанной на одном языке доступен набор типов, который может отличаеться от программы написанной на другом языке. И все же, можно выделить два базовых типа, характерных для подавляющего большинства языков программирования. Это числовой и строковый типы. Кому-то может показаться, что такое разделение излишне - ведь и число можно хранить в виде строки.
На этом этапе важно понять, что разные языки предназначены для разных уровней разработки. Например, в языках высокого уровня (таких как perl) не имеет значения строковые данные хранит переменная или числовые. Компилятор (или интерпретатор) самостоятельно определяет как ему интерпретировать значение в зависимости от контекста. Если необходимо выполнить арифметическое сложение, то выполняется попытка преобразовать значение переменной к числовому типу. Если же выполняется конкатенация или интерполяция строки, то любой числовой операнд будет автоматически преобразован к строковому типу.
Для языков более низкого уровня такое разделение более характерно, так как команды процессора работают с числами и строками по разному. А если спустится на самый нижний уровень (то есть на уровень команд процессора, которые в конечном итоге представляют любую программу не зависимо от языка разработки) 32х разрядных процессоров, то такое разделение просто необходимо. Необходимость разделения этих двух типов диктуется тем, что, как я уже говорил, числовые и строковые данные по разному хранятся в памяти. Помимо того, на уровне ассемблера мы увидим всего три типах данных: байт, слово и двойное слово. Последние два представляют собой типы, состоящие из нескольких экземпляров типа байт. Слово состоит из двух, а двойное слово, соответственно, из четырех байт. То есть, в конечном итоге, данные любого типа преобразуются к одному из этих трех числовых типов. Теперь должно быть понятно, почему говорят, что компьютер работает только с числами.
Методика представления символьных (или текстовых) данных в компьютере не так проста, как может показаться на первый взгляд. Во всяком случае, работать со строками гораздо сложнее, нежели с целыми числами. Большинство компиляторов реализуют свои механизмы работы с символьными данными, скрывая от программиста рутинный часто-использующийся код.
Как же хранится в памяти строковое значение? Любой текст состоит из символов и представляет собой определенную последовательность, для которой порядок расположения имеет первостепенное значение. Если не учитывать порядок то смысл текста будет утерян. Каждый символ определяется числовым значением - кодом. Теперь мы можем представить строку как последовательность чисел. Ну а с числами мы уже разобрались.
Теперь давайте разберемся со второй категорией типов данных - пользовательскими типами. Прилагательное "пользовательские" не совсем ясно отражает суть этой категории типов на данном этапе рассмотрения, правильнее было бы называть эти типы расширенными. Однако, это определение сразу же проясняется, когда мы начинаем говорить об этих типах как о наборах, состоящих из базовых типов. Проще всего уяснить суть на примерах. Для простоты мы будем рассматривать программу на языке perl, в котором тип единичного значения не существенен. Например, название улицы, номер дома и номер квартиры можно объединить в набор, представляющий собой новый тип значения - "адрес проживания". При этом, заметьте, что составные элементы нового типа представляют собой значения базовых типов: одного строкового и двух числовых. Однако, компилятор (или интерпретатор) не знает как обрабатывать новый тип "адрес проживания", он лишь знает как его хранить (так как знает способ хранения данных базовых типов). Программист должен реализовать механизм обработки самостоятельно.