Работаем с «заданиями» (Job)
Буквально на неделе на форуме появились два интересных вопроса, ответ на которые был очевиден, но… Программист, как вы знаете, существо с очень пытливым мозгом,
Буквально на неделе на форуме появились два интересных вопроса, ответ на которые был очевиден, но… Программист, как вы знаете, существо с очень пытливым мозгом,
Это перевод Why does each drive have its own current directory? Автор: Реймонд Чен.
Комментатор Dean Earley спросил: «Почему есть ‘текущий каталог’ и ‘текущий диск’? Почему бы их не объединить?»
Лаконичный ответ: потому что изначально у каждого диска был свой собственный каталог. Сейчас это не так, даже если иногда вам кажется иначе.
Хорошо, давайте развернём это утверждение. Вообще-то, вы уже знаете ответ на этот вопрос, вам только нужно сложить все кусочки вместе.
Заведите вашу машину времени на времена DOS 1.0. Каждый дисковый том был представлен буквой диска. На дисках вообще не было каталогов. Такая структура пришла из CP/M.
Поэтому программы эры DOS 1.0 не понимали и не умели работать с каталогами, они ссылались на файлы просто указанием буквы диска и имени файла. Например: B:PROGRAM.LST
. Давайте запустим ассемблер (компиляторы в то время были для богачей) и соберём программу с исходным кодом на дискете A, а результат запишем на дискету B:
A>asm foo |
расширение «.asm» у «foo» подразумевается неявно | |
Assembler version blah blah blah |
||
Source File: FOO.ASM |
||
Listing file [FOO.LST]: NUL |
листинг нам не нужен | |
Object file [FOO.OBJ]: B: |
сохранить объектник на диск B |
Поскольку на запрос Object file
мы указали только букву диска, то ассемблер возьмёт имя по умолчанию (FOO.OBJ
), а итоговое имя файла для записи объектника будет B:FOO.OBJ
.
Окей, теперь давайте выпустим DOS 2.0 с поддержкой подкаталогов. Предположим, вы хотите собрать исходный код A:SRCFOO.ASM
и положить результат в B:OBJFOO.OBJ
. Вот как вы можете сделать это:
A> B:
B> CD OBJ
B> A:
A> CD SRC
A> asm foo
Assembler version blah blah blah
Source File: FOO.ASM
Listing file [FOO.LST]: NUL
Object file [FOO.OBJ]: B:
Ассемблер читает файл A:FOO.ASM
и записывает файл B:FOO.OBJ
, но поскольку текущий каталог — свой у каждого диска, то результаты идут в A:SRCFOO.ASM
и B:OBJFOO.OBJ
— как нам и нужно. Если бы текущий каталог был глобальным, то мы не смогли бы указать ассемблеру, чтобы он поместил вывод в подкаталог. Иными словами, тогда любые программы DOS 1.0 (а в момент выхода DOS 2.0 других программ просто не было) были бы ограничены корневым каталогом, что в свою очередь означало бы, что никто не использовал бы подкаталоги, т.к. их программы не могли получить доступ к этим файлам!
С точки зрения DOS 1.0, изменение текущего каталога диска эквивалентно смене дискеты. «Ой, смотри, теперь на этом диске совсем другой набор файлов!»
Да, такая вот краткосрочная память.
Это запоминание текущего каталога для каждого диска сохраняется в системе и сегодня — хотя бы для командных файлов, поскольку Win32 не имеет концепции «текущего каталога диска«. В Win32 у вас есть «просто» текущий каталог. Видимость, что текущий каталог — свой у каждого диска, поддерживается cmd.exe
с помощью странных переменных окружения.
Dean продолжает: «Почему бы не слить эти два понятия? Сейчас для смены текущего каталога на конкретный мне нужно устанавливать и каталог и диск».
Ответ на второй вопрос звучит так: «Вообще-то эти понятия объединены в 1995 году. Это только cmd.exe
делает вид, что они различны». И если вы хотите сменить и диск и каталог одной командой, то просто добавьте параметр /D
к команде CHDIR
:
D:> CD /D C:Program FilesWindows NT
C:Program FilesWindows NT> _
Заметьте, что команда CHDIR
позволяет вам не указывать кавычки вокруг путей с пробелами, поскольку у этой команды есть всего один аргумент, так что отсутствие кавычек не приводит к неоднозначности трактовки.
Это перевод FPO. Автор: Ларри Остерман.
На прошлой неделе я болтал с одним парнем из команды измерения производительности. Он мне рассказал историю, которая меня очень удивила. Как оказалось, они обнаружили проблему производительности в одном стороннем драйвере. К сожалению, у них возникли проблемы в локализации узкого места, поскольку разработчик драйвера скомпилировал его с FPO (Frame Pointer Ommission) и не предоставил отладочную информацию.
Что тут удивительного? Ну, я удивился что сегодня вообще кто-то использует FPO.
Но что такое FPO?
Чтобы понять ответ, нам нужно вернуться назад во времени.
Процессор Intel 8088 имел чрезвычайно мало регистров, вот они (я игнорировал сегментные регистры):
AX | BX | CX | DX | IP |
SI | DI | BP | SP | FLAGS |
Даже в таком ограниченном наборе регистров этим регистрам были заданы специальные роли. Регистры AX
, BX
, CX
и DX
были регистрами «общего назначения», SI
и DI
были «индексными» регистрами, SP
был «указателем стека», BP
был «указателем фрейма» («указателем базы»), IP
был «указателем инструкции», а FLAGS
был регистром только для чтения, который содержал информацию о текущем состоянии процессора.
Регистры BX
, SI
, DI
и BP
были специальными, потому что они могли использоваться как «индексные» регистры. Индексные регистры чрезвычайно важны для компилятора, поскольку только они могут использоваться в получении доступа к памяти через указатель. Другими словами, если у вас есть структура, расположенная по смещению $1234
в памяти, вы можете установить индексный регистр в значение $1234
и получать доступ к значениям, относительно этого адреса (и регистра). Например:
MOV BX, [Structure]
MOV AX, [BX]+4
Мы записываем в регистр BX
значение памяти, на которое указывает [Structure]
, а затем записываем в регистр AX
слово, находящееся в 4 байте с начала этой структуры.
Здесь нужно отметить, что регистр SP
не являлся индексным регистром. Это означало, что для получения доступа к локальным переменным и аргументам на стеке вам нужно использовать другой регистр — и именно так появился регистр BP
. Индексный регистр BP
был создан специально для получения доступа к значениям в стеке.
Когда вышел 386, разработчики Intel расширили регистры до 32 бит, а также сняли ограничение, что только BX
, SI
, DI
и BP
могли использоваться как индексные:
EAX | EBX | ECX | EDX | EIP |
ESI | EDI | EBP | ESP | FLAGS |
Это неплохо: неожиданно, вместо жалких трёх регистров, компилятор мог использовать все шесть.
Поскольку индексные регистры используются для доступа к полям структур, для компилятора они на вес золота — чем их больше, тем лучше, так что стоит пойти на многое, лишь бы их было побольше.
Некий очень умный человек сообразил, что раз теперь ESP
является индексным регистром, то EBP
больше не является единственным специально выделенным регистром для стека. Другими словами, вместо:
MyFunction:
PUSH EBP
MOV EBP, ESP
SUB ESP, <LocalVariableStorage>
MOV EAX, [EBP+8]
:
:
MOV ESP, EBP
POP EBP
RETD
вы могли получить доступ к первому параметру на стеке следующим образом (EBP
+ 0 — старое значение EBP
, EBP
+ 4 — адрес возврата):
MyFunction:
SUB SP, <LocalVariableStorage>
MOV EAX, [ESP+4+]
:
:
ADD SP, <LocalVariableStorage>
RETD
Это работает на отлично, теперь EBP
освобождается и его можно использовать как дополнительный регистр общего назначения! Такая оптимизация в компиляторе называется «Frame Pointer Omission», известная под акронимом FPO.
Но с FPO есть небольшая проблема.
Если вы посмотрите на первый вариант кода (без FPO), то заметите, что первой инструкцией в функции будет PUSH EBP
, за которым будет MOV EBP, ESP
. Здесь получается интересный и крайне полезный (побочный) эффект. Фактически, при этом получается односвязный список, в котором хранятся указатели на каждый фрейм для всех вызывающих функции. Таким образом, зная значение EBP
внутри одной функции, вы можете получить стек вызовов для этой функции. Это невероятно полезно для отладки, потому что это означает, что стеки вызовов всегда точны — даже если у вас на руках нет отладочной информации. К сожалению, если вы начинаете использовать FPO, список фреймов теряется — эта информация просто не отслеживается.
Чтобы решить эту проблему, информация, которая теряется при использовании FPO, заносится в отладочную информацию. Таким образом, если у вас есть отладочная информация («символы»), то вы сможете построить стек вызовов.
FPO был включен для всех модулей Windows, начиная с NT 3.51. Но начиная с Windows Vista FPO был отключен — поскольку он стал не нужен: машины стали значительно быстрее машин 1995 года, так что небольшой выигрыш в производительности от FPO не покрывал его пенальти на отладку и анализ.
Эту статью я написал потому, что сам очень хотел прочитать такую — о сервисах Яндекса, авторизации OAuth, о том, как держать бубен, чтобы коробочный Indy
Мне очень нравятся Build Events в Delphi. Это когда с проектом можно связать некую команду (например, cmd-скрипт), которая будет выполняться перед сборкой (или после сборки) приложения. Будь то по F9, либо при внешней сборке с помощью MSBuild. Мне это нужно для актуализации автогенерируемого кода, используемого проектом.
На днях занимался оптимизацией Prebuild-события, постоянно правил свой cmd-скрипт, и мне очень быстро надоело переключаться между Delphi и Notepad++ (в котором редактировал скрипт). Немного покопавшись в ToolsApi, у меня получилось сделать подсветку синтаксиса для CMD/BAT-файлов.
Выглядит это так:
Это в меню Tools Options. Как это выглядит в самом редакторе – см. ниже.
Пока сделал на скорую руку (осторожно, черновой код!), исходник доступен тут: https://github.com/delphinotes/Useful/tree/master/CMDHighlighter
Устанавливается так:
А вот как выглядит кусочек моего Prebuild-скрипта:
Спасибо за внимание 🙂
При работе с беспроводным подключением устройства, часто приходится запускать bat файл с командами подключения для adb.
Например таким:
C:Androidsdkplatform-toolsadb connect 192.168.0.10:5555
Чтобы ускорить эту работу, можно добавить запуск bat скрипта прямо в Android Studio.
В этом нам поможет инструмент External Tools.
В нем теоретически можно настроить запуск любой сторонней программы.
Для добавления скрипта идём в File->Settings->Editor->External Tools и нажмем +
Далее все интуитивно понятно.
Как сделать свои горячие клавиши, я уже объяснял тут.
Для наглядности приведу окно настройки
Ctrl+Shift+Q и устройство подключено (если конечно ip не сменился)