mirror of
https://github.com/ellysh/bash-programming-from-scratch.git
synced 2026-01-26 07:43:42 +00:00
Translate the "Application" section
This commit is contained in:
@ -4,29 +4,4 @@ mainmatter:
|
||||
|
||||
GeneralInformation/README.md
|
||||
GeneralInformation/operating-system.md
|
||||
GeneralInformation/application.md
|
||||
|
||||
BashShell/README.md
|
||||
BashShell/tools.md
|
||||
BashShell/command-line-interface.md
|
||||
BashShell/file-system.md
|
||||
BashShell/commands-info.md
|
||||
BashShell/files-and-directories.md
|
||||
BashShell/extra-features.md
|
||||
|
||||
BashScripting/README.md
|
||||
BashScripting/tools.md
|
||||
BashScripting/scripts-features.md
|
||||
BashScripting/variables.md
|
||||
BashScripting/conditional-statement.md
|
||||
BashScripting/arithmetic-expression.md
|
||||
BashScripting/loop-operators.md
|
||||
BashScripting/functions.md
|
||||
|
||||
package-manager.md
|
||||
|
||||
finalwords.md
|
||||
|
||||
glossary.md
|
||||
answers.md
|
||||
links.md
|
||||
GeneralInformation/application.md
|
||||
@ -1,201 +1,92 @@
|
||||
## Компьютерная программа
|
||||
## Computer Program
|
||||
|
||||
Мы рассмотрели семейства современных ОС и их функции. Под их управлением работают компьютерные программы. Именно они решают прикладные задачи пользователей. Программа представляет собой набор элементарных шагов или инструкций. Выполняя их последовательно компьютер справляется со сложными задачами: проигрывание аудио и видео, обработка электронных документов, передача сообщений по сети и т.д. Рассмотрим подробнее, как происходит запуск и исполнение программ.
|
||||
We got acquainted with operating systems. They are responsible for starting and running computer programs. The programs solve the user's application tasks. For example, a text editor allows working with text.
|
||||
|
||||
### Память компьютера
|
||||
A program is a set of elementary steps or instructions. The computer performs these steps sequentially. This way, it copes with complex tasks. Let us consider in detail how the program is launched and executed.
|
||||
|
||||
Инструкции любой компьютерной программы исполняются центральным процессором. Они хранятся на жёстком диске или другом носителе информации в виде файла. Когда вы его запускаете, ОС сначала загружает содержимое этого файла в оперативную память и только потом исполняет инструкции программы.
|
||||
### Computer Memory
|
||||
|
||||
Чтобы понять, как ОС загружает программу, надо разобраться в устройстве памяти компьютера. Но перед этим будет полезно познакомиться с единицами её измерения — [**байтами**](https://ru.wikipedia.org/wiki/Байт). Байт — это минимальный блок информации, на который может ссылаться процессор и загружать в свою память. В то же время процессор способен оперировать меньшими объёмами информации чем один байт — битами. [**Бит**](https://ru.wikipedia.org/wiki/Бит) — это минимальная единица информации, которая не может быть разложена на составные части. Бит представляет собой логическое состояние с двумя возможными значениями. Эти значения можно интерпретировать разными способами: 0 или 1, истина или ложь, да или нет, + или —, включено или выключено. Проще всего представить себе бит, как воображаемый выключатель лампы. Он может либо замыкать цепь (лампа горит) или размыкать (тогда она выключена). Восемь битов составляют блок в один байт.
|
||||
The computer program instructions are stored on a hard disk as a file. For launching the application, the OS loads the content of this file into RAM. Then the OS allocates processor time for program execution. At specified intervals, the processor executes its instructions.
|
||||
|
||||
Упаковка битов в байты вызывает вопросы. Если операции над отдельными битами допускаются, почему нельзя ссылаться в памяти на конкретный бит? На это есть исторические причины. Первые компьютеры использовались преимущественно для вычислений (например, [баллистических таблиц](https://ru.wikipedia.org/wiki/Баллистическая_таблица)). Они оперировали целыми и дробными числами. Чтобы закодировать любое большее единицы число, одного бита недостаточно. Поэтому возникла потребность в более крупных блоках, достаточных для хранения одного числа. Такими блоками стали группы битов или байты. Впоследствии объединение битов отразилось на архитектуре процессоров. Ожидалось, что большая часть операций компьютера будет происходить над числами, то есть блоками битов. Поэтому намного эффективнее загружать и обрабатывать их как единое целое.
|
||||
Let's see how the OS loads the program into RAM. We start with how computer memory works.
|
||||
|
||||
Остаётся открытым ещё один вопрос. Почему один байт состоит именно из восьми бит? В ранних компьютерах размер байта равнялся [шести битам](https://ru.wikipedia.org/wiki/Шестибитные_кодировки). Такого блока было достаточно для кодирования всех символов английского алфавита в верхнем и нижнем регистре, цифр, знаков пунктуации и математических операций. Скоро этого размера стало не хватать и байт был расширен до семи битов. Этот момент совпал с появлением [ASCII-таблицы](https://ru.wikipedia.org/wiki/ASCII). Она стала стандартом для кодирования символов. Именно поэтому в ASCII-таблице определены символы для кодов от 0 до 127, то есть до максимального семибитного числа. После этого IBM выпустила мейнфрейм [IBM System/360](https://ru.wikipedia.org/wiki/IBM_System/360). Он стал очень популярен и получил широкое распространение. В нём размер байта составлял восемь битов. Это решение позволяло поддерживать старые кодировки символов из прошлых проектов IBM. Постепенно такая упаковка битов стала стандартом в отрасли.
|
||||
The single unit of the computer memory is [**byte**](https://en.wikipedia.org/wiki/Byte). The byte is the minimum amount of information that the processor can reference and load into its memory. The CPU can handle smaller amounts of data. They are called bits. The [**bit**](https://en.wikipedia.org/wiki/Bit) is the minimum amount of information that cannot be broken down. The bit is a logical state with two possible values. There are several ways to interpret the value of the bit:
|
||||
|
||||
Часто используемые единицы объёма информации приведены в таблице 1-1.
|
||||
* 0 or 1
|
||||
* True or False
|
||||
* Yes or No
|
||||
* + or —
|
||||
* On or Off.
|
||||
|
||||
{caption: "Таблица 1-1. Единицы объёма информации", width: "70%"}
|
||||
| Название | Сокращение | Число байтов | Число битов |
|
||||
Imagine a bit like a lamp switch. It has two possible states:
|
||||
|
||||
* The switch closes the circuit. Then the lamp is on.
|
||||
* The switch opens the circuit. Then the lamp is off.
|
||||
|
||||
Eight bits make up a memory block of one byte.
|
||||
|
||||
Packaging bits in bytes raises questions. Operations with individual bits are possible. Why cannot the processor refer to a specific bit in memory? This limitation has historical reasons. The first computers did arithmetic calculations mainly. The example is calculating the [ballistic tables](https://www.wikiwand.com/en/Ballistic_table). For solving such tasks, the computers operated with integers and fractional numbers. One bit is not enough to store a number in memory. Therefore, memory blocks that are larger than the bits needed. Such blocks are bytes. The combining bits into bytes reflected on the architecture of processors. Processor designers expected that most calculations are performed over numbers. Therefore, loading and processing all bits of numbers at a time increases computer performance by order of magnitude. So, it led that processors work with bytes only.
|
||||
|
||||
There is one more question. Why does a byte consist of eight bits? In the first computers, a byte's size was equal to [six bits](https://en.wikipedia.org/wiki/Six-bit_character_code). Such a memory block was enough to encode all the English alphabet characters in upper and lower case, numbers, punctuation marks and mathematical operations. Over time, this size has become insufficient. The byte was expanded to seven bits. At that moment, the [ASCII table](https://en.wikipedia.org/wiki/ASCII) appeared. The table became the standard for encoding characters. The ASCII table defines characters for codes from 0 to 127. 127 is the maximum seven-bit number. Later, IBM has released the mainframe [IBM System/360](https://en.wikipedia.org/wiki/IBM_System/360). The size of a byte was eight bits in this computer. IBM chose this size for supporting old character encodings from the past projects. Such packaging of bits has become the industry standard. It happened because of the popularity and widespread of IBM System/360.
|
||||
|
||||
Table 1-1 shows frequently used [units of information](https://en.wikipedia.org/wiki/Units_of_information).
|
||||
|
||||
{caption: "Таблица 1-1. Units of information", width: "70%"}
|
||||
| Title | [Abbreviation](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/bits-bytes-terms) | Number of bytes | Number of bits |
|
||||
| --- | --- | --- | --- |
|
||||
| килобайт | Кбайт | 1000 | 8000 |
|
||||
| мегабайт | Мбайт | 1000000 | 8000000 |
|
||||
| гигабайт | Гбайт | 1000000000 | 8000000000 |
|
||||
| терабайт | Тбайт | 1000000000000 | 8000000000000 |
|
||||
| kilobyte | KB | 1000 | 8000 |
|
||||
| megabyte | MB | 1000000 | 8000000 |
|
||||
| gigabyte | GB | 1000000000 | 8000000000 |
|
||||
| terabyte | TB | 10000000000 | 8000000000000 |
|
||||
|
||||
В таблице 1-2 приведены распространённые устройства хранения информации и их объёмы.
|
||||
Table 1-2 shows standard storage devices and their capacity.
|
||||
|
||||
{caption: "Таблица 1-2. Устройства хранения информации", width: "50%"}
|
||||
| Устройство хранения | Объём |
|
||||
{caption: "Table 1-2. Storage devices", width: "50%"}
|
||||
| Storage device | Capacity |
|
||||
| --- | --- |
|
||||
| [Дискета 3.5"](https://ru.wikipedia.org/wiki/Дискета) | 1.44 Мбайт |
|
||||
| [Компакт-диск](https://ru.wikipedia.org/wiki/Компакт-диск) | 700 МБайт |
|
||||
| [DVD-диск](https://ru.wikipedia.org/wiki/DVD) | до 17 Гбайт |
|
||||
| [USB-флеш-накопитель](https://ru.wikipedia.org/wiki/USB-флеш-накопитель) | до 2 Тбайт |
|
||||
| [Жёсткий диск](https://ru.wikipedia.org/wiki/Жёсткий_диск) | до 16 Тбайт |
|
||||
| [Твердотельный накопитель](https://ru.wikipedia.org/wiki/Твердотельный_накопитель) | до 100 Тбайт |
|
||||
| [Floppy disk 3.5"](https://en.wikipedia.org/wiki/Floppy_disk) | 1.44 MB |
|
||||
| [Compact disk](https://en.wikipedia.org/wiki/Compact_disc) | 700 MB |
|
||||
| [DVD](https://en.wikipedia.org/wiki/DVD) | up to 17 GB |
|
||||
| [USB flash drive](https://en.wikipedia.org/wiki/USB_flash_drive) | up to 2 TB |
|
||||
| [Hard disk drive](https://en.wikipedia.org/wiki/Hard_disk_drive) | up to 16 TB |
|
||||
| [Solid State Drive](https://en.wikipedia.org/wiki/Solid-state_drive) | up to 100 TB |
|
||||
|
||||
Мы познакомились с единицами измерения памяти. Теперь вернёмся к исполнению программы. Почему её необходимо загружать в оперативную память? Разве нельзя сразу исполнить инструкции из файла, хранящегося на жёстком диске?
|
||||
We got acquainted with units of information. Now let's get back to the execution of the program. Why does the OS load it into RAM? The processor could read the program instructions directly from the hard disk drive.
|
||||
|
||||
В современном компьютере вся память разделена на [четыре уровня](https://ru.wikipedia.org/wiki/Иерархия_памяти). Они изображены на иллюстрации 1-13. Это физическое разделение памяти. То есть каждому уровню соответствуют разные устройства. Единственное исключение — процессор. В кристалле процессора находятся и регистры, и кэш память. Но это разные модули кристалла.
|
||||
All memory is divided into [four levels](https://en.wikipedia.org/wiki/Memory_hierarchy) in a modern computer. Each level matches the red rectangle in Figure 1-13. There is a physical division of memory. Memory levels match different devices. The only exception is the processor. In the CPU chip, there are both registers and memory cache. But these are separate modules of the chip.
|
||||
|
||||
Стрелки на иллюстрации 1-13 соответствуют потокам данных. Передача данных возможна только между соседними уровнями памяти. Процессор для своей работы может использовать только то, что хранится в его регистрах. Поэтому если ему нужны данные с дисковой памяти, они будут сначала загружены в оперативную память, затем в кэш CPU и только потом попадут в регистры. Аналогичным образом происходит и обратный процесс записи данных на диск.
|
||||
The arrows in Figure 1-13 represent data flows. Data transfer occurs only between adjacent memory levels. The processor works with data from its registers only. If the CPU needs something from the disk drive, it is loaded in the following way:
|
||||
|
||||
{caption: "Иллюстрация 1-13. Уровни памяти персонального компьютера", height: "50%"}
|
||||

|
||||
1. Disk drive -> RAM
|
||||
2. RAM -> Processor cache
|
||||
3. Processor cache -> Processor registers
|
||||
|
||||
Уровни памяти отличаются друг от друга несколькими параметрами:
|
||||
Data is written to the disk drive in reverse order of steps.
|
||||
|
||||
1. **Скорость доступа** определяет объём данных, читаемый или записываемый в единицу времени на носитель. Она измеряется в [**байтах**](https://ru.wikipedia.org/wiki/Байт) за секунду (байт/с).
|
||||
{caption: "Figure 1-13. Memory hierarchy", height: "50%"}
|
||||

|
||||
|
||||
2. **Объём** — количество данных, которое может храниться на носителе. Измеряется в байтах.
|
||||
The memory levels differ from each other by the following parameters:
|
||||
|
||||
3. **Стоимость** — цена носителя в соотношении к его объёму. Измеряется в долларах или центах за байт или бит.
|
||||
1. **Access speed** means how much data is read or written to the media per unit of time. Units of measure are bytes per second (KBps).
|
||||
|
||||
4. **Время доступа** — время между моментом, когда данные понадобились процессору, и моментом, когда они станут ему доступны. Измеряется в [**тактовых сигналах**](https://ru.wikipedia.org/wiki/Тактовый_сигнал) процессора.
|
||||
2. **Capacity** is the maximum amount of data that a medium can store. The units are bytes.
|
||||
|
||||
Соотношение рассмотренных параметров для разных типов памяти приведено в таблице 1-3.
|
||||
3. **Cost** is a price of a medium concerning its capacity. The units are dollars or cents per byte or bit.
|
||||
|
||||
{caption: "Таблица 1-3. Уровни памяти персонального компьютера", width: "100%", column-widths: "10%,30%,*"}
|
||||
| Уровень | Память | Объём | Скорость доступа | Время доступа | Стоимость |
|
||||
4. **Access time** is the time between when data was needed and when it became available to the processor. Units are [**clock signals**](https://en.wikipedia.org/wiki/Clock_signal) of the CPU.
|
||||
|
||||
Table 1-3 shows the ratio of parameters of different memory types.
|
||||
|
||||
{caption: "Table 1-3. Memory levels", width: "100%", column-widths: "10%,30%,*"}
|
||||
| Level | Memory | Capacity | Access speed | Access time | Cost |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | [**Регистры**](https://ru.wikipedia.org/wiki/Регистр_процессора) процессора. | до тысячи байтов | — | 1 такт | — |
|
||||
| | | | | | |
|
||||
| 2 | [**Кэш**](https://ru.wikipedia.org/wiki/Кэш_процессора) память процессора. | от одного килобайта до нескольких мегабайтов | от 700 до 100 гигабайт/сек | от 2 до 100 тактов | — |
|
||||
| | | | | | |
|
||||
| 3 | Оперативная память | десятки гигабайтов | 10 гигабайт/сек | до 1000 тактов | $10^-9^/байт |
|
||||
| | | | | | |
|
||||
| 4 | Дисковая память ([**жёсткие диски**](https://ru.wikipedia.org/wiki/Жёсткий_диск) и [**твёрдотельные накопители**](https://ru.wikipedia.org/wiki/Твердотельный_накопитель)) | терабайты | 2000 мегабайт/сек | до 10000000 тактов | $10^-12^/байт |
|
||||
| 1 | [**CPU registers**](https://en.wikipedia.org/wiki/Processor_register) | up to 1000 bytes | - | 1 tick | - |
|
||||
| | | | | | |
|
||||
| 2 | [**CPU cache**](https://en.wikipedia.org/wiki/CPU_cache) | from one KB to several MB | from 700 to 100 GBps | from 2 to 100 cycles | - |
|
||||
| | | | | | |
|
||||
| 3 | RAM | dozens of GB | 10 GBps | up to 1000 clock cycles | $10^-9^/byte |
|
||||
| | | | | | |
|
||||
| 4 | Disk drive ([**hard drive**](https://en.wikipedia.org/wiki/Hard_disk_drive) or [**solid drive**](https://en.wikipedia.org/wiki/Solid-state_drive)) | several TB | 2000 Mbps | up to 10000000 cycles | $10^-12^/byte |
|
||||
|
||||
Таблица 1-3 вызывает вопросы. Скорости доступа к дисковой памяти должно быть достаточно для чтения и исполнения файла приложения размером с десятки мегабайт. Зачем загружать этот файл сначала в оперативную памяти, а потом в кэш процессора? Разве нельзя сразу передавать данные с диска в регистры? На самом деле важна не скорость чтения данных в байтах, а то как долго простаивает процессор, дожидаясь доступа к ним. Это время доступа к памяти измеряется в числе тактовых сигналов или тактах. Такт синхронизирует выполнение всех операций процессора. Как правило, одна инструкция программы выполняется в течении одного или нескольких тактов.
|
||||
|
||||
Если бы процессору приходилось читать инструкции программы с жёсткого диска и записывать на него промежуточные данные и результаты, выполнение простейших алгоритмов заняло бы в лучшем случае недели. Причём большую часть этого времени процессор простаивал бы, ожидая выполнения операций чтения и записи. Иерархическая организация памяти на порядки ускоряет доступ к данным, необходимых процессору. Тем самым решается проблема его простаивания. Как это происходит?
|
||||
|
||||
Представьте, что процессор исполняет программу. Например, она читает файл с диска и выводит его содержимое на экран. При этом, согласно иллюстрации 1-13, данные с диска сначала будут загружены в оперативную память. Затем по частям они загружаются в кэш процессора, а оттуда в его регистры. После этого CPU вызовет функцию из системной библиотеки ОС, в которую эти данные передаются. Функция с помощью драйвера видеокарты выведет данные на экран. Проблема может возникнуть, когда процессор вызвал функцию и переходит к следующему блоку данных из файла. Если окажется, что они ещё не загружены в регистры из кэша, то CPU проведёт в ожидании от 2 до 100 тактов (согласно таблице 1-3). Аналогично, если данные ещё не были загружены из RAM в кэш, то время ожидания вырастет на порядок (до 1000 тактов). Предположим, что читаемый файл оказался слишком велик и не поместился целиком в оперативную память. Тогда возможна ситуация, когда его часть, необходимая в данный момент процессору, еще не была загружена в RAM. Тогда время простоя CPU увеличится на 4 порядка (до 10000000 тактов). Вместо этого ожидания процессор мог бы выполнить около 1000000 инструкций какой-нибудь программы.
|
||||
|
||||
Надеюсь, этот пример помог вам оценить, насколько дорого обходятся ошибка механизма кэширования, называемая **промахом**. Программист должен всегда помнить об иерархии памяти и учитывать её при разработке своих алгоритмов. Некоторые алгоритмы приводят к большему числу промахов, чем другие.
|
||||
|
||||
Обратите внимание, что чем меньше время доступа к памяти, тем ближе она физически расположена к процессору (см. иллюстрацию 1-14). Например, внутренняя память CPU (регистры и кэш) находится внутри его кристалла. Оперативная память (RAM) расположена на [**материнской плате**](https://ru.wikipedia.org/wiki/Материнская_плата) рядом с процессором и соединена с ним по высокочастотной [**шине данных**](https://ru.wikipedia.org/wiki/Шина_данных). Дисковая память подключается к материнской плате через относительно медленную шину данных (например, [**SATA**](https://ru.wikipedia.org/wiki/SATA)).
|
||||
|
||||
I> За загрузку данных из RAM в кэш процессора отвечает системный контроллер под названием [**северный мост**](https://ru.wikipedia.org/wiki/Северный_мост_(компьютер)). В ранних версиях персональных компьютеров он представлял собой отдельный чип на материнской плате. В дальнейшем с развитием технологии изготовления процессоров в их кристаллы стали встраивать северный мост. За чтение данных с жёсткого диска в оперативную память отвечает контроллер под названием [**южный мост**](https://ru.wikipedia.org/wiki/Южный_мост_(компьютер)).
|
||||
|
||||
{caption: "Иллюстрация 1-14. Материнская плата ПК", height: "50%"}
|
||||

|
||||
|
||||
### Машинный код
|
||||
|
||||
Предположим, что ОС успешно загрузила содержимое исполняемого файла приложения в оперативную память. В этом файле хранятся не только инструкции программы, но и необходимые данные для её работы. Этими данными могут быть текстовые строки, иконки, картинки, предопределённые константы и т.д. Инструкции программы называются [**машинным кодом**](https://ru.wikipedia.org/wiki/Машинный_код). За исполнение каждой из них отвечает определённый логический блок процессора. Набор этих блоков определяет поддерживаемые CPU операции. Если процессор не имеет блока для выполнения какой-то операции, она всё равно может быть выполнена с помощью комбинации других блоков. Но такое исполнение будет неоптимальным, потому что займёт больше времени и ресурсов. В любом случае машинная инструкция считается элементарной операцией над данными, загруженными в регистры CPU.
|
||||
|
||||
После загрузки программы в оперативную память CPU начинает её исполнение. Это исполнение называется [**вычислительным процессом**](https://ru.wikipedia.org/wiki/Процесс_(информатика)) (process). К процессу также относятся ресурсы, которые использует работающая программа: память и объекты ОС.
|
||||
|
||||
Есть специальные программы для чтения и редактирования исполняемых файлов. Они называются [**Hex-редакторами**](https://ru.wikipedia.org/wiki/Hex-редактор). Такие редакторы представляют машинный код программы в [**шестнадцатеричной системе счисления**](https://ru.wikipedia.org/wiki/Шестнадцатеричная_система_счисления). На самом деле в исполняемом файле хранится [**двоичный код**](https://ru.wikipedia.org/wiki/Двоичный_код#Примеры_двоичных_чисел). Этот код представляет собой последовательность байтов, то есть нулей и единиц. Hex-редактор для удобства чтения переводит последовательность байтов в шестнадцатеричный формат. Именно в двоичном коде процессор получает инструкции и данные.
|
||||
|
||||
Одно и то же число можно представить в разных системах счисления. Система счисления определяет, какие символы и в каком порядке используются при записи числа. Например, двоичная система допускает только символы 0 и 1. Таблица 1-4 демонстрирует соответствие чисел в двоичной (binary, BIN), десятичной (decimal, DEC) и шестнадцатеричной (hexadecimal, HEX) системах счисления.
|
||||
|
||||
{caption: "Таблица 1-4. Числа в системах счисления: DEC, HEX и BIN", width: "50%"}
|
||||
| Десятичная | Шестнадцатеричная | Двоичная |
|
||||
| --- | --- | --- |
|
||||
| 0 | 0 | 0000 |
|
||||
| 1 | 1 | 0001 |
|
||||
| 2 | 2 | 0010 |
|
||||
| 3 | 3 | 0011 |
|
||||
| 4 | 4 | 0100 |
|
||||
| 5 | 5 | 0101 |
|
||||
| 6 | 6 | 0110 |
|
||||
| 7 | 7 | 0111 |
|
||||
| 8 | 8 | 1000 |
|
||||
| 9 | 9 | 1001 |
|
||||
| 10 | A | 1010 |
|
||||
| 11 | B | 1011 |
|
||||
| 12 | C | 1100 |
|
||||
| 13 | D | 1101 |
|
||||
| 14 | E | 1110 |
|
||||
| 15 | F | 1111 |
|
||||
|
||||
I> Для перевода из одной системы счисления в другую используйте стандартный калькулятор Windows. Функция перевода доступна в [**режиме "Программист"**](https://ru.wikipedia.org/wiki/Калькулятор_(Windows)#Режим_«Программист»).
|
||||
|
||||
Почему в программировании наряду с десятичной системой активно используются двоичная и шестнадцатеричная? На двоичной системе и булевой алгебре строится вся современная [**цифровая техника**](https://ru.wikipedia.org/wiki/Цифровые_технологии). В цифровой технике элементарным носителем информации является электрический [**сигнал**](https://ru.wikipedia.org/wiki/Сигнал). Самый экономичный способ кодирования сигнала заключается в различении двух состояний: когда он есть и когда его нет. Наличие сигнала кодируется единицей, а отсутствие — нулём. То есть для кодирования достаточно одного бита.
|
||||
|
||||
[**Логический вентиль**](https://ru.wikipedia.org/wiki/Логический_вентиль) — базовый элемент в цифровой технике. Он преобразовывает электрические сигналы. Физически эти элементы могут принципиально различаться. На разных этапах развития компьютеров их роль выполняли: электромагнитные реле, электровакуумные лампы и транзисторы. Но все эти устройства работают одинаково с точки зрения обработки сигналов. Эта обработка состоит из двух действий: получения одного или двух сигналов на вход и передача на выход одного результирующего сигнала. Такая обработка выполняется по правилам [**булевой алгебры**](https://ru.wikipedia.org/wiki/Булева_алгебра), также известной как [**алгебра логики**](https://ru.wikipedia.org/wiki/Алгебра_логики). То есть для каждой операции этой алгебры существует соответствующий логический вентиль. Если соединить их последовательно, получается сложное преобразование сигналов. По сути центральный процессор есть не что иное, как огромная сеть логических элементов. Двоичная система счисления позволяет работать с цифровой техникой на самом низком уровне, то есть на уровне электрических сигналов. Получается, что использование этой системы в программировании продиктовано особенностью работы аппаратуры.
|
||||
|
||||
Зачем в таком случае понадобилась шестнадцатеричная система для разработки программ? На самом деле программисты в своей работе используют либо десятичную систему, либо двоичную. Первая удобна при написании высокоуровневой логики программы. Например, для расчёта того, сколько раз надо повторить то или иное действие. Двоичная система применяется, когда программе приходится взаимодействовать с аппаратурой. Например, для подготовки и передачи данных на устройство. Проблема двоичной системы в том, что она неудобна человеку для записи, чтения, запоминания и произношения. Перевод же из DEC в BIN достаточно сложен. Именно эту проблему перевода чисел решает шестнадцатеричная система. Она так же компактна и удобна для человека, как и десятичная. Перевод из неё в двоичную систему и обратно можно производить в уме.
|
||||
|
||||
Чтобы перевести число из двоичной системы в шестнадцатеричную, разбейте его на группы по четыре разряда, начиная с конца. Если последняя группа оказалась меньше четырёх разрядов дополните её нулями впереди. Затем по таблице 1-4 каждую четвёрку замените на шестнадцатеричное число. Рассмотрим пример такого перевода:
|
||||
```
|
||||
110010011010111 = 110 0100 1101 0111 = 0110 0100 1101 0111 = 6 4 D 7 = 64D7
|
||||
```
|
||||
|
||||
{caption: "Упражнение 1-1. Перевод чисел из BIN в HEX", line-numbers: false}
|
||||
```
|
||||
Переведите следующие числа из двоичной системы в шестнадцатеричную:
|
||||
* 10100110100110
|
||||
* 1011000111010100010011
|
||||
* 1111101110001001010100110000000110101101
|
||||
```
|
||||
|
||||
{caption: "Упражнение 1-2. Перевод чисел из HEX в BIN", line-numbers: false}
|
||||
```
|
||||
Переведите следующие числа из шестнадцатеричной системы в двоичную:
|
||||
* FF00AB02
|
||||
* 7854AC1
|
||||
* 1E5340ACB38
|
||||
```
|
||||
|
||||
Ответы на все упражнения приведены в последнем разделе книги. Если вы не уверены в правильности своего результата, сверьтесь с ответами.
|
||||
|
||||
Вернёмся к нашему исполняемому файлу. Кроме него в оперативную память загружаются все необходимые для работы приложения библиотеки (в том числе системные). За эту процедуру отвечает [**загрузчик программ Windows**](https://ru.wikipedia.org/wiki/Загрузчик_программ). Благодаря предварительной загрузке библиотек, процессору не приходится ждать, когда программа к ним обращается. Весь код библиотеки уже в памяти и доступен CPU в течении нескольких сотен тактов. После окончания работы загрузчика Windows программа считается процессом и исполняется CPU, начиная со своей первой инструкции.
|
||||
|
||||
После окончания работы программы занимаемая ей и загруженными библиотеками область RAM очищается. Она может использоваться другими приложениями.
|
||||
|
||||
### Исходный код
|
||||
|
||||
Машинный код, представленный в двоичном виде, понятен процессору. Однако, человеку писать программу на нём очень неудобно. Особенно остро эта проблема стала проявляться с увеличением мощности компьютеров и усложнении их программ. Для её решения появились специальные приложения: [**компиляторы**](https://ru.wikipedia.org/wiki/Компилятор) и [**интерпретаторы**](https://ru.wikipedia.org/wiki/Интерпретатор). Оба типа приложений решают одну и ту же задачу. Эта задача состоит из двух шагов:
|
||||
|
||||
1. Прочитать текст программы, написанный на удобном для человека языке.
|
||||
|
||||
2. Перевести текст в машинный код.
|
||||
|
||||
Программы пишут на [**языках программирования**](https://ru.wikipedia.org/wiki/Язык_программирования). Они отличаются от [**естественного языка**](https://ru.wikipedia.org/wiki/Естественный_язык), на котором мы с вами общаемся. Главное отличие языков программирования заключается в искусственном ограничении того, что можно на них выразить. Нет смысла описывать в программе что-то, что компьютер не способен выполнить. Также языки программирования отличаются строгими правилами. Например, набор допустимых слов ограничен и они должны следовать в определённом порядке. Текст программы, записанный на языке программирования, называется [**исходным кодом**](https://ru.wikipedia.org/wiki/Исходный_код).
|
||||
|
||||
Компиляторы и интерпретаторы работают с исходным кодом по-разному. Отличие заключается в моменте, когда машинный код генерируется из исходного. Компиляторы читают текст программы целиком, генерируют инструкции процессора и сохраняют результат в файл на диске. При этом программа не исполняется. Интерпретаторы читают исходный код по частям, генерируют инструкции процессора и сразу же их исполняют. Результат работы интерпретатора временно хранится в оперативной памяти.
|
||||
|
||||
Рассмотрим пример компиляции программы. Предположим, что вы написали и сохранили её исходный код в файл на жёстком диске. Дальше вы запускаете компилятор того языка программирования, который вы использовали. Результат компиляции программы будет сохранён в новый исполняемый файл на диске. Он содержит машинный код, соответствующий исходному коду вашей программы. Теперь, чтобы выполнить программу, достаточно запустить исполняемый файл.
|
||||
|
||||
Иллюстрация 1-15 демонстрирует процесс компиляции программы, написанной на языке C или C++.
|
||||
|
||||
{caption: "Иллюстрация 1-15. Компиляция программы", height: "50%", width: "100%"}
|
||||

|
||||
|
||||
Согласно иллюстрации, компиляция состоит из двух этапов. Первый выполняется компилятором. Второй этап под названием **линковка** выполняется специальной программой [**компоновщиком**](https://ru.wikipedia.org/wiki/Компоновщик).
|
||||
|
||||
При линковке создаются промежуточные **объектные файлы**. Может возникнуть вопрос — зачем они нужны? Почему нельзя объединить компилятор и линковщик в одну программу? У такого решения есть две проблемы. Первая заключается в ограниченном размере оперативной памяти. Исходный код программы обычно разбивается на несколько файлов. Компилятор, работая с ним, строит **промежуточное представление программы**. Это представление хранится в RAM и может по различным причинам значительно превосходить по размеру файл с исходным кодом. Чтобы получить исполняемый файл, компилятор должен пройти по всему исходному коду. Сохранять промежуточные результаты на диск нельзя. В этом случае очень высока вероятность, что оперативной памяти просто не хватит.
|
||||
|
||||
Вторая проблема заключается в разрешении зависимостей. Функции каждого из трёх [**текстовых файлов**](https://ru.wikipedia.org/wiki/Текстовый_файл) с исходным кодом на иллюстрации 1-15 могут вызывать друг друга. Компилятору потребовалось бы намного больше времени, чтобы сопоставить эти перекрёстные вызовы сразу в процессе компиляции. Кроме того возможны случаи, когда для получения исполняемого файла необходима дополнительная библиотека. В этом случае она выполняет роль ещё одного объектного файла, который подаётся на вход компоновщика. Из-за этих проблем разделение компиляции на два этапа даёт более надёжное и гибкое решение.
|
||||
|
||||
Теперь предположим, что для исполнения программы вы выбрали интерпретатор, а не компилятор. В этом случае файл с исходным кодом уже готов для исполнения. Чтобы его запустить, ОС сначала загружает интерпретатор. Далее интерпретатор читает файл с исходным кодом с диска в оперативную память и начинает его выполнение строка за строкой. При этом преобразование каждой команды исходного кода в машинный код происходит в оперативной памяти. В целях оптимизации некоторые интерпретаторы сохраняют на жёсткий диск файлы с промежуточным представлением программы. Но главная идея в том, что программу всегда исполняет интерпретатор.
|
||||
|
||||
Иллюстрация 1-16 демонстрирует процесс интерпретации программы.
|
||||
|
||||
{caption: "Иллюстрация 1-16. Интерпретация программы", height: "50%", width: "100%"}
|
||||

|
||||
|
||||
Задумайтесь над проблемами компиляции за один этап, которые мы рассмотрели. Теперь посмотрите ещё раз на иллюстрацию 1-16. Вам не кажется, что интерпретатор работает как "одноэтапный" компилятор? Он загружает все текстовые файлы в оперативную память, строит их промежуточное представление, а затем разрешает зависимости. Как ему это удаётся?
|
||||
|
||||
На самом деле интерпретатор обрабатывает исходный код не так как компилятор. Он читает и выполняет программу строка за строкой. Это значит, что ему не нужно хранить в памяти код всего приложения и его промежуточное представление. Достаточно обрабатывать исходный код по мере надобности. При нехватке оперативной памяти, уже обработанные команды могут быть выгружены.
|
||||
|
||||
Любой интерпретатор работает медленно. Загрузка исходного кода программы с диска в RAM и его выгрузка обходится дорогими простоями процессора. Согласно таблице 1-3, эти простои могут длиться до 10000000 тактов. Кроме того сам интерпретатор — это сложная программа. Для работы она требует часть аппаратных ресурсов компьютера. Получается, что в нагрузку к вашей программе компьютер будет параллельно выполнять и инструкции интерпретатора. Эти накладные расходы приводят к тому, что интерпретация программ — это дорогой вычислительный процесс.
|
||||
|
||||
Интерпретация программ обходится дорого, а что насчёт компиляции? Накладные расходы есть и у компилятора. Он генерирует исполняемый файл с машинным кодом. Поэтому скорость выполнения скомпилированного приложения почти такая же, как и написанного изначально на машинном коде. Вы платите за удобство языка программирования на этапе компиляции. Генерация машинного кода для небольшого приложения требует нескольких секунд и незначительных ресурсов компьютера. Но для больших и сложных проектов (например, ядро ОС) это время увеличивается до нескольких часов. Любое изменение исходного кода приведёт к повторной компиляции. Помните про накладные расходы, при выборе языка программирования для вашего проекта. Одни задачи лучше решаются интерпретатором, а другие — компилятором.
|
||||
|
||||
Стоит ли вообще использовать языки программирования? Часы ожидания компиляции проекта, можно было бы потратить на разработку программы в машинном коде. Таким образом мы бы избежали лишнего расхода времени и аппаратных ресурсов на работу интерпретатора. Но обратимся к примеру, чтобы оценить преимущества, которые дают языки программирования. Листинг 1-1 демонстрирует исходный код программы на языке C. Она выводит на консоль текст "Hello world!".
|
||||
|
||||
{caption: "Листинг 1-1. Исходный код программы на языке C", format: C}
|
||||

|
||||
|
||||
В листинге 1-2 приведена та же программа в виде машинного кода в шестнадцатеричном представлении.
|
||||
|
||||
{caption: "Листинг 1-2. Машинный код программы"}
|
||||

|
||||
|
||||
Очевидно, что код из листинга 1-1 намного проще прочитать, понять и отредактировать. Возможно код из листинга 1-2 и просто написать, но разобраться в нём другому программисту будет очень сложно.
|
||||
|
||||
Любой язык программирования удобнее и выразительнее машинного кода. Благодаря этому, написанные на нём программы намного проще разрабатывать и поддерживать.
|
||||
Table 1-3 raises questions. The speed of access to disk drives is huge. Why is it impossible to read data from the disk into registers directly? Actually, the access speed is not so important. More critical is processor idle time. How long does the processor idle waiting for access to the requested data? The unit of this idle time is the number of clock signals. The signal synchronizes all operations of the processor. It takes one or several clock cycles for executing a single program instruction.
|
||||
Reference in New Issue
Block a user