top of page

Глава 2. Часть 3. Сражение на «ферромагнитных сердечниках» или Переключение процессов

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

Взгляните на рисунок. Представьте себе, что две программы - простейшая Imp и Близнецы находятся в памяти MARS'а на некотором расстоянии друг от друга. Программа, начинающая игру, выбирается произвольно - предположим, что это будет Imp. На самом первом шаге MARS передаёт управление процессу Imp, то есть попросту выполняет его текущую инструкцию MOV 0,1.

На втором шаге (а время в компьютерах дискретно - запомним это раз и навсегда) MARS передаёт управление второму процессу - программе Близнецы - и выполяет его первую инструкцию MOV @-2,@-1.

Следующий шаг - управление снова передано Imp - выполняется следующая инструкция этого процесса (которая тоже оказывается инструкцией MOV 0,1) - и так далее, до бесконечности, или до того момента, когда один из процессов будет убит. Кстати, по правилам Core Wars процесс погибает (проигрывает), если его следующей инструкцией оказывается псевдоинструкция DAT, другими словами - некое число, или просто "мусор" в памяти.

Как видите, идея переключения процессов очень проста. Но на этом история не заканчивается - новые сюрпризы ещё впереди!

После того, как первая статья о Core Wars была опубликована, полные энтузиазма читатели предложили множество дополнений и изменений первой версии игры. Одно из них было принято Дьюдни, и в своей следующей статье он сообщил, что решил расширить набор инструкций Redcode новой инструкцией SPL (split). Эта инструкция "расщепляет" программу одного из игроков надвое. После её выполнения вместо одного процесса на стороне того же игрока начинают выполняться два процесса. Операндом этой инструкции служит адрес, с которого начинается новый процесс, при том, что первоначальный процесс продолжает выполняться со следующей за SPL инструкции. Например:

После выполнения инструкции SPL 4, новый процесс того же игрока следующим шагом выполнит инструкцию по адресу 4 (то есть MOV 0,1), а ещё следующим шагом старый процесс того же игрока выполнит следующую за SPL инструкцию (ADD #1,-100).

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

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

(Предположим, например, что у первого игрока есть три параллельных процесса: А1, А2 и А3, а у второго - два: В1 и В2. Тогда последовательность их выполнения будет выглядеть следующим образом: А1, В1, А2, В2, А3, В1, А1, В2, А2, В1, А3, В2, ...)

***

В заключение можно добавить, что в последующие коды язык Redcode значительно усложнился. В нём появилось несколько новых инструкций, дополнительные способы адресации (с предварительным вычитанием указателя, с последующим увеличением указателя), различные модификаторы полей инструкций, так называемый p-space (который можно перевести как private, personal или permanent space) - это та часть памяти, которая доступна программе только одного из игроков - и разные другие усовершенствования. Делают ли они игру интересней? Возможно. Но эстетика простоты и минимализма, представленная в оригинальной статье Дьюдни, к сожалению, исчезла.

Небольшое отступление об интроспекции

Рассматривая программу «Близнецы», мы коснулись очень интересной темы – «интроспекции». Этим философским термином в программировании называется процесс «подсматривания» программой в саму себя. Результатом такого «подсматривания» в простом случае может быть копирование программой самой себя в другое место в памяти или распечатывание своего собственного текста[1], а в сложном случае – «узнавание» программой некоторых своих особенностей с последующим использованием этих знаний[2].

В качестве отступления рассмотрим следующую «интроспективную» программу на С, которая распечатывает свой собственный текст[3]:

main(a){a=”main(a){a=%c%s%c;printf(a,34,a,34);}”;

printf(a,34,a,34);}

Постараемся понять, что же здесь происходит.

Прежде всего, а – это указатель на строку форматирования, используемую впоследствии в функции printf: ”main(a){a=%c%s%c;printf(a,34,a,34);}”.

Следующая (и последняя) команда программы – это вызов функции printf, первым аргументом которой является только что созданная строка форматирования a. printf напечатает строку «main(a) {a=» после чего вместо первого %c подставит символ с ASCII кодом 34, то есть кавычки - “, вместо %s подставит саму строку a в ее оригинальном виде (то есть включая знаки процента и флаги форматирования), и, наконец, вместо последнего %с еще раз распечатает кавычки. При этом строка а еще не закончена – функции printf осталось распечатать заключительное, «;printf(a,34,a,34);}» - и дело сделано !

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

#!/usr/bin/env python

x = “””print ‘#!usr/bin/env python’

print ‘x = “”’+’”’+x+’””’+’”’

print x”””

print ‘#!/usr/bin/env python’

print ‘x = “”’+’”’+x+’””’+’”’

print x

А также ещё одна программа на языке Scheme:

(define x '( (display "(define x '(") (newline) (map (lambda (s) (write s) (newline)) x) (display "))") (newline) (display "(map eval x)") (newline) )) (map eval x)

 

[1] Такие программы называются "Quine" - название придумано Дугласом Хофштадтером в его книге "Gödel, Escher, Bach" в честь американского философа Уилларда Куайна.

[2] В современных языках программирования (таких как Java и C#) такая возможность называется «reflection» и используется в основном для «компонентного программирования», то есть собирания программы из независимых компонентов прямо во время исполнения.

[3] У этой программы есть два недостатка: во-первых, почти всегда включение директивы препроцессора #include <stdio.h> является обязательным; во-вторых, будучи скомпилированной компилятором gcc, программа не работает. Зато она работает в Visual C++.

Related Posts

bottom of page