приблизительно эквивалентные функции по частям (как это сделано в Windows), или попытаться заново создать весь мир (как попыталась и не смогла BeOS). Однако тем временем в Unix-системах с открытым исходным кодом росли клиентские возможности по использованию GUI-интерфейсов, а также возможности работы на недорогих персональных машинах.
Данные факторы оказались, однако, не настолько уравновешенными, как это может показаться из приведенного выше описания. Модернизация таких функций серверных операционных систем, как множество классов привилегий и полная многозадачность, в клиентской операционной системе представляется очень трудной задачей, и, весьма вероятно, нарушит совместимость с предыдущими версиями клиента, а также приведет к многочисленным проблемам безопасности. С другой стороны, проблем связанных с внедрением GUI-интерфейса в серверную операционную систему, вполне можно избежать при условии наличия высококвалифицированного персонала и недорогих аппаратных ресурсов. Как и в строительстве, восстановить надстройку на поверхности прочного фундамента проще, чем заменить фундамент без разрушения надстройки.
Кроме наличия собственных архитектурных преимуществ серверной операционной системы, Unix всегда скептически относилась к целевой аудитории. Разработчики и создатели никогда не допускали, что знают все потенциально возможные способы применения системы.
Следовательно, конструкция операционной системы Unix проявила гораздо большие возможности по модернизации в качестве клиента, чем любая из клиентских операционных систем-конкурентов продемонстрировала возможности по модернизации в качестве сервера. Хотя возрождению Unix в 90-х годах прошлого века способствовало и множество других факторов, как технологических, так и экономических, но именно вышеупомянутые возможности выходят на первый план при обсуждении стиля проектирования операционных систем.
Часть II
Проектирование
4
Модульность: четкость и простота
Есть два способа конструирования программного обеспечения. Один из них заключается в том, чтобы создавать такие простые программы, в которых нет недостатков; другой — в том, чтобы создавать программы настолько сложные, чтобы в них не было очевидных недостатков. Первый метод значительно труднее.
Существует естественная иерархия методов разделения кода на блоки, которая развивалась по мере того, как программистам приходилось управлять возрастающими уровнями сложности. Вначале все программы представляли собой огромную глыбу машинного кода. Ранние процедурные языки внесли понятие разделения на подпрограммы. Затем были изобретены служебные библиотеки для совместного использования общих полезных функций между множеством программ. После этого были разработаны отдельные адресные пространства и взаимодействующие процессы. В настоящее время не вызывает удивления тот факт, что программные системы распределяются среди множества узлов, разделенных тысячами миль сетевого кабеля.
Ранние разработчики Unix были в числе первопроходцев модульности программного обеспечения. До их прихода правило модульности было теорией компьютерной науки, но не инженерной практикой. В книге
Здесь следует акцентировать внимание на правиле модульности: единственным способом создания сложного программного обеспечения, которое будет надежно работать, является его построение из простых модулей, соединенных четкими интерфейсами, с тем чтобы большинство проблем были локальными, а также была большая вероятность наладки или оптимизации какой-либо его части без нарушения конструкции в целом.
Традиция заботиться о модульности и уделять пристальное внимание проблемам ортогональности и компактности до сих пор среди Unix-программистов проявляется гораздо ярче, чем в любой другой среде.
Программисты ранней Unix стали мастерами модульности, поскольку были вынуждены сделать это. Операционная система является одним из наиболее сложных блоков кода. Если она не структурирована должным образом, то она развалится. Было несколько неудачных попыток построения Unix. Можно было бы отнести это на счет раннего (еще без структур) С, однако в основном это происходило ввиду того, что операционная система была слишком сложна в написании. Прежде чем мы смогли 'обуздать' эту сложность, нам потребовалось усовершенствование инструментов (таких как структуры С)и хорошая практика их применения (например, правила Роба Пайка для программирования).
Первые Unix-хакеры решали данную проблему различными способами. В 1970 году вызовы функций в языках программирования были очень затратными либо ввиду запутанной семантики вызовов (PL/1, Algol), либо из-за того, что компилятор был оптимизирован для других целей, например, для увеличения скорости внутренних циклов за счет времени вызова. Таким образом, код часто писали в виде больших блоков. Кен и несколько других первых Unix-разработчиков знали, что модульность является хорошей идеей, однако они помнили о PL/1 и неохотно писали небольшие функции, чтобы не снизить производительность.
Деннис Ритчи поддерживал модульность, повторяя всем без исключения, что вызовы функций в С крайне, крайне малозатратны. Все начали писать небольшие функции и компоновать программы из модулей. Через несколько лет выяснилось, что вызовы функций оставались дорогими на PDP-11, а код VAX часто тратил 50% времени на обработку инструкции CALLS. Деннис солгал нам! Однако было слишком поздно; все мы попались на удочку...
Всех сегодняшних программистов, пишущих для Unix или других операционных систем, учат создавать модули внутри программ на уровне подпрограмм. Некоторые учатся делать это на уровне модулей или абстрактных типов данных и называют это 'хорошим программированием'. Сторонники использования моделей проектирования прилагают благородные усилия для повышения уровня разработки и создают все новые успешные абстрактные модели, которые можно применять для организации крупномасштабной структуры программ.