Программирование с нуля Как устроен компьютер: биты, память, процессор и файлы простыми словами
0%

Как устроен компьютер: биты, память, процессор и файлы простыми словами

Как устроен компьютер: биты, память, процессор и файлы простыми словами

Сразу успокою: чтобы написать первую программу, знать устройство компьютера не обязательно. Многие начинают с кода и разбираются потом. Но есть момент, который наступает у всех примерно на второй неделе: программа выдаёт странную ошибку, и объяснение звучит как «файл не найден», хотя файл вот он, на экране. Или число внезапно оказывается 0.30000000000000004 вместо 0.3.

В этот момент человек либо говорит «компьютеры — это магия, я не понимаю», либо вспоминает базовую картину и спокойно чинит проблему. Эта статья — про базовую картину. Мы не будем лезть в физику транзисторов. Нам хватит четырёх идей: бит, память, процессор, файл.

Читать её лучше не спеша, с открытым Python под рукой — хотя, если Python ещё не установлен, ничего страшного, установку мы делаем в статье Первая программа, и туда можно вернуться позже.


Компьютер очень глупый, но чудовищно быстрый

Первое, что стоит принять: компьютер не «понимает». Он не догадывается, не додумывает, не прощает опечатки. Он умеет делать буквально несколько простых вещей: сложить два числа, сравнить два числа, взять число из одной ячейки и положить в другую, перейти к другой инструкции.

Всё остальное — видеозвонки, игры, нейросети — построено из этих простых действий, которых выполняются миллиарды в секунду. Ощущение «умной машины» возникает не от сложности операций, а от их количества.

Аналогия. Представьте человека, который умеет только складывать однозначные числа, но делает это со скоростью миллиард действий в секунду и никогда не устаёт. Дайте ему достаточно бумаги и очень подробную инструкцию — и он посчитает вам траекторию ракеты. Именно этим и занимается программист: пишет очень подробную инструкцию для очень быстрого и очень буквального исполнителя.

Где аналогия ломается: человек-вычислитель поймёт «ну ты понял, что я имею в виду». Компьютер — нет. Если в инструкции написано «раздели на ноль», он попытается разделить на ноль и остановится с ошибкой. Буквальность — не недостаток, а свойство, на которое мы опираемся: одна и та же программа при одних и тех же данных всегда даёт один и тот же результат.


Бит: самая маленькая единица информации

Внутри компьютера нет цифр 0–9 и нет букв. Есть миллиарды крошечных элементов, каждый из которых находится в одном из двух состояний: есть напряжение или нет, намагничено или нет.

Одно такое состояние называется бит (от англ. binary digit — двоичная цифра). Записывают его как 0 или 1.

Почему именно два состояния, а не десять? Потому что «есть сигнал / нет сигнала» — это то, что железо различает надёжно даже при помехах. А различить десять уровней напряжения миллиард раз в секунду без ошибок гораздо сложнее. Двоичная система — это не красота математики, это инженерный компромисс в пользу надёжности.

Как из двух цифр получаются любые числа

Один бит — два варианта: 0 или 1. Два бита — уже четыре комбинации: 00, 01, 10, 11. Три бита — восемь. Каждый новый бит удваивает количество вариантов.

Мы привыкли к десятичной системе, где разряды — это единицы, десятки, сотни (степени десятки). В двоичной ровно та же логика, только разряды — это 1, 2, 4, 8, 16, 32 (степени двойки).

Число 1000010 в двоичной системе читается справа налево:

Разряд 64 32 16 8 4 2 1
Бит 1 0 0 0 0 1 0

Складываем те разряды, где стоит единица: 64 + 2 = 66.

Проверим это в Python. Функция bin() показывает число в двоичном виде, а int(строка, 2) переводит двоичную запись обратно в обычное число:

# bin() превращает обычное число в строку с его двоичной записью.
# Префикс "0b" — просто пометка "дальше идут двоичные цифры", он не часть числа.
print(bin(66))          # смотрим, как 66 выглядит в двоичном виде
print(bin(5))           # и заодно маленькое число для сравнения

# int(строка, 2) делает обратное: читает строку как двоичное число.
# Вторая цифра 2 — это основание системы счисления.
print(int("1000010", 2))

Ожидаемый вывод:

0b1000010
0b101
66

Обратите внимание: 66 и 0b1000010 — это одно и то же число, записанное по-разному. Как «двенадцать», «12» и «XII». Внутри компьютера оно в любом случае хранится битами; десятичная запись существует только для нас, людей.

Байт: восемь бит, ставших стандартом

Работать с отдельными битами неудобно, поэтому их сгруппировали по восемь. Восемь бит — это байт.

Сколько разных значений помещается в байт? Каждый из 8 битов даёт двойку вариантов, значит 2 в восьмой степени:

print(2 ** 8)        # ** — это возведение в степень
print(bin(255))      # самое большое число, влезающее в один байт
256
0b11111111

256 вариантов: от 0 до 255. Отсюда растут ноги у множества «странных» чисел в IT — 256 цветов, адреса вида 192.168.0.1 (каждое число там — один байт), ограничение «максимум 255 символов» в старых системах.

Дальше идут привычные приставки: килобайт (примерно тысяча байт), мегабайт (миллион), гигабайт (миллиард). Строго говоря, в компьютерном мире исторически считали по 1024, а не по 1000, — поэтому купленный «терабайтный» диск в системе показывает 931 ГБ. Диск не обманул, просто производитель считал по 1000, а система по 1024.

Буквы — это тоже числа

Раз внутри только биты, то буква «А» тоже должна быть числом. Так и есть. Люди договорились о таблице соответствий: какое число какую букву обозначает. Это называется кодировка.

# ord() показывает числовой код символа
print(ord("A"))      # латинская заглавная A
print(ord("a"))      # латинская строчная a — это другой символ и другое число
print(ord("П"))      # кириллица живёт в далёких номерах

# chr() делает обратное — по числу возвращает символ
print(chr(66))
65
97
1055
B

Именно поэтому "A" и "a" для компьютера — разные вещи: 65 и 97, разница как между 5 и 7. Компьютер не «видит букву», он сравнивает числа.

Сегодняшний стандарт — Unicode (таблица, где есть символы всех письменностей мира, эмодзи и математические знаки), а самый популярный способ записать его в байты — UTF-8. В UTF-8 латинская буква занимает 1 байт, кириллическая — 2, эмодзи — обычно 4:

text = "Привет"

# len() для строки считает СИМВОЛЫ — то, что видит человек
print(len(text))

# .encode("utf-8") превращает строку в последовательность байтов —
# то, что реально уйдёт в файл или в сеть
raw = text.encode("utf-8")
print(len(raw))
print(raw)
6
12
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

Шесть букв — двенадцать байт. Буква b перед кавычками означает «это байты, а не текст», а \xd0 — способ записать один байт в шестнадцатеричном виде. Запоминать шестнадцатеричную систему прямо сейчас не нужно; достаточно понимать, что перед вами сырые байты, а не буквы.

Эта разница между «символами» и «байтами» — источник половины проблем с кракозябрами. Если текст сохранили в одной кодировке, а прочитали в другой, компьютер честно применит не ту таблицу и покажет привет вместо привет. Файл при этом не испорчен — просто его читают не теми «очками».


Память: длинная улица пронумерованных ячеек

Биты надо где-то держать. Место, где программа держит данные прямо сейчас, называется оперативная память (ОЗУ, RAM).

Представьте очень длинную улицу с одинаковыми ящиками. Каждый ящик хранит ровно один байт, и у каждого есть номер — адрес. Процессор умеет сказать: «дай содержимое ящика номер 4 812 366» — и получить его почти мгновенно, независимо от того, где этот ящик находится. Отсюда и английское название: Random Access Memory, память произвольного доступа — любой ящик доступен одинаково быстро.

Схема: память как улица пронумерованных ячеек, одни и те же байты как число и как строка

Ключевая мысль со схемы: сами биты не несут смысла. Байт 01000010 — это одновременно число 66, буква «B» и часть какой-нибудь картинки. Смысл появляется только тогда, когда программа решает, как их читать. Именно поэтому в программировании так важны типы данных — они и есть эта договорённость о правилах чтения. Подробно разберём в статье Переменные и типы данных.

Оперативная память и диск — это разные вещи

Новички часто путают «память» и «место на диске». Разница принципиальная.

Оперативная память (RAM) Диск (SSD/HDD)
Зачем данные, с которыми работают прямо сейчас долгое хранение
Скорость очень высокая в сотни-тысячи раз медленнее
При выключении всё стирается сохраняется
Объём 8–32 ГБ типично 256 ГБ – 4 ТБ типично

Аналогия: оперативная память — это письменный стол, диск — шкаф с папками. На стол вы выкладываете то, с чем работаете сейчас; в шкафу лежит всё остальное. Стол маленький, но всё под рукой; шкаф большой, но за папкой надо встать и пойти.

Где аналогия ломается: уходя домой, вы не сметаете стол в мусорку — а компьютер при выключении делает именно это. Всё, что было в оперативной памяти, исчезает. Поэтому несохранённый документ пропадает при отключении света: он жил на «столе», а в «шкаф» его не положили.

Из этой же разницы растёт понимание, почему программы вообще работают с файлами: чтобы результат пережил завершение программы, его нужно осознанно записать на диск. Об этом — статья Файлы и форматы данных.

Память быстрая не одинаково

На самом деле «улица ящиков» — не одна. Их несколько уровней, и чем ближе к процессору, тем быстрее и меньше:

Регистры — это несколько крошечных ячеек внутри самого процессора, в которых он держит числа, над которыми работает буквально в эту наносекунду. Кэш — небольшой быстрый склад рядом с процессором, куда автоматически копируются недавно использованные данные.

Аналогия: регистры — это то, что вы держите в руках; кэш — стол; оперативная память — полка за спиной; диск — шкаф в подвале; сеть — библиотека в другом городе. Каждый шаг вниз — примерно в 10–100 раз дольше.

Начинающему программисту не нужно управлять кэшем вручную — это делает железо. Но полезно запомнить: обращение к диску и к сети в тысячи раз дороже, чем к памяти. Когда позже вы будете думать, почему программа тормозит, ответ чаще всего окажется не «мало вычислений», а «слишком много походов в подвал».


Процессор: вечный цикл из трёх шагов

Процессор (CPU, центральное процессорное устройство) — та самая деталь, которая выполняет инструкции. Устроен его рабочий день предельно однообразно: он крутит один и тот же цикл, пока компьютер включён.

Всё. Никакой магии: взял инструкцию, понял, сделал, записал, взял следующую. Процессор с частотой 3 ГГц прокручивает этот цикл около трёх миллиардов раз в секунду.

Пара понятий, которые вы будете встречать постоянно:

  • Тактовая частота (гигагерцы) — сколько «тиков» в секунду делает процессор. Один такт — один шажок работы. Больше герц — быстрее, но сравнивать по частоте процессоры разных поколений бессмысленно: за один такт они успевают разный объём работы.
  • Ядро — по сути отдельный процессор в одном корпусе. Четыре ядра — четыре инструкции могут выполняться по-настоящему одновременно. Аналогия: не «повар работает быстрее», а «на кухне стало четыре повара». И, как на настоящей кухне, четыре повара не готовят блюдо ровно в четыре раза быстрее — им нужно договариваться, кто берёт какую сковородку.
  • Архитектура (x86-64, ARM) — «язык», на котором процессор принимает инструкции. Программа, скомпилированная под один язык, не запустится на другом без перевода. Именно поэтому приложения для ноутбуков Apple на чипах M-серии и для обычных ПК — разные файлы.

Что происходит, когда вы запускаете программу

Соберём картину целиком. Вы дважды кликнули на иконку или набрали python my_program.py:

Операционная система (Windows, macOS, Linux) — это программа-распорядитель. Она решает, кому из запущенных программ сейчас дать процессор, следит, чтобы одна программа не залезла в память другой, и предоставляет всем общий доступ к диску, экрану и сети. Запущенная программа со своим кусочком памяти называется процесс.

Отдельный вопрос — как ваш текст на Python вообще превращается в инструкции процессора. Коротко: Python-код читает специальная программа-интерпретатор, которая шаг за шагом выполняет написанное вами, сама обращаясь к процессору. Поэтому Python-программе нужен установленный Python, а, скажем, скомпилированной программе на Go — нет: её заранее перевели в машинные инструкции целиком. Разницу подробнее разберём в Первой программе.

Побочный эффект битов: та самая история с 0.1 + 0.2

Теперь можно объяснить самую известную «странность», на которую натыкается каждый новичок:

print(0.1 + 0.2)
0.30000000000000004

Это не баг Python — то же самое покажут JavaScript, Java и C. Причина в битах: дробные числа хранятся в двоичной системе, а 0.1 в двоичной записи — бесконечная дробь, ровно как 1/3 = 0.333... в десятичной. Компьютер вынужден округлить её до доступного числа битов, и крошечная погрешность при сложении вылезает наружу.

Практический вывод: для денег и точных расчётов не используют обычные дробные числа (в Python для этого есть модуль decimal), а сравнивать дробные числа через == — плохая идея. Формат хранения описан в стандарте IEEE 754, а подробный разбор есть на floating-point-gui.de.


Файлы: имена, данные и договорённости

Файл — это именованная последовательность байтов на диске. Всё. Никаких других свойств у него нет.

Это стоит перечитать, потому что отсюда следует несколько неочевидных вещей.

Расширение — это просто часть имени. .txt, .jpg, .py — не встроенное свойство файла, а хвост его названия, по которому операционная система догадывается, какой программой его открыть. Переименовав фото.jpg в фото.txt, вы не превратили картинку в текст — байты остались теми же, просто система теперь ошибётся в выборе программы.

import os

# splitext() разрезает имя на две части по ПОСЛЕДНЕЙ точке.
# Это чисто работа со строкой — файл при этом даже не открывается.
print(os.path.splitext("report.final.csv"))
('report.final', '.csv')

Текстовые и бинарные файлы отличаются только договорённостью. Внутри и там и там байты. Если байты задуманы как символы в какой-то кодировке — файл называют текстовым и открывают в режиме "r". Если это картинка, видео или архив — бинарным, режим "rb" (b — binary). Попытка прочитать картинку как текст даст ошибку именно потому, что байты не складываются в валидные символы.

Папки и пути

Файлы лежат в папках (каталогах), папки — в других папках. Получается дерево:

Путь — это маршрут до файла. Путей два вида, и путаница между ними даёт самую частую ошибку новичка.

  • Абсолютный путь начинается от корня и полностью определяет место: /home/anna/projects/my-first-program/main.py (Linux, macOS) или C:\Users\anna\projects\my-first-program\main.py (Windows). Аналогия: полный почтовый адрес с городом и улицей.
  • Относительный путь отсчитывается от папки, в которой программа работает прямо сейчас: data.csv или notes/idea.txt. Аналогия: «вторая дверь налево» — работает, только если вы стоите в нужном коридоре.

Та самая папка, «в которой программа работает сейчас», называется текущей рабочей директорией. Её легко посмотреть:

import os

# getcwd = get current working directory, "получить текущую рабочую папку"
print(os.getcwd())

# listdir показывает, что лежит в указанной папке.
# Точка "." означает "текущая папка"
print(os.listdir("."))

Вывод у каждого свой, например:

/home/anna/projects/my-first-program
['main.py', 'data.csv', 'notes']

Важный момент: текущая папка — это не папка, где лежит файл программы. Это папка, из которой программу запустили. Если вы из домашней директории набрали python projects/my-first-program/main.py, то текущей будет домашняя, и относительный путь data.csv укажет не туда, куда вы ожидали.

Напишем маленькую программу, которая создаёт файл и читает его обратно:

# Открываем файл на запись: "w" = write.
# Если файла нет — он создастся; если есть — содержимое будет стёрто.
# encoding="utf-8" явно говорит, какой таблицей кодировать буквы —
# без него Windows может выбрать свою и получатся кракозябры.
with open("hello.txt", "w", encoding="utf-8") as f:
    f.write("Привет, диск!\n")   # \n — это символ перевода строки

# Конструкция with ... as f сама закрывает файл, когда блок закончится.
# Это важно: пока файл не закрыт, данные могут ещё не дойти до диска.

# Теперь читаем обратно: "r" = read
with open("hello.txt", "r", encoding="utf-8") as f:
    content = f.read()

print(content)
print(len(content), "символов")
Привет, диск!

13 символов

Тринадцать символов — это 12 букв со знаками препинания плюс невидимый перевод строки. Пустая строка в выводе появилась как раз из-за него: \n внутри текста плюс перевод строки от print().


Типичные ошибки новичков и что означают сообщения

Сообщения об ошибках выглядят пугающе, но они устроены логично: последняя строка — суть проблемы. Читайте снизу вверх. Разберём три, с которыми сталкиваются буквально все.

FileNotFoundError

FileNotFoundError: [Errno 2] No such file or directory: 'data.csv'

Дословно: «ошибка номер 2, нет такого файла или папки: data.csv».

В девяти случаях из десяти файл существует — просто программа ищет его не там, потому что data.csv это относительный путь. Что делать:

import os
print("Ищу здесь:", os.getcwd())     # где программа сейчас "стоит"
print("Вижу тут:", os.listdir("."))  # что она отсюда видит

Если в списке нужного файла нет — либо перейдите в правильную папку перед запуском, либо укажите абсолютный путь. Второй частый вариант: в Windows включено скрытие расширений, и файл на самом деле называется data.csv.txt.

UnicodeDecodeError

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte

Дословно: «кодировщик utf-8 не может расшифровать байт 0xcf на позиции 0».

Перевод на человеческий: вы попросили читать байты как текст в UTF-8, но эти байты по правилам UTF-8 не складываются в символ. Либо файл сохранён в другой кодировке (частый гость — cp1251 для старых русских текстов), либо это вообще не текст, а картинка или архив.

Лечится указанием правильной кодировки: open(path, encoding="cp1251") — или чтением в бинарном режиме "rb", если файл и не должен быть текстом.

PermissionError

PermissionError: [Errno 13] Permission denied: 'report.xlsx'

Дословно: «доступ запрещён». Обычно означает, что файл уже открыт другой программой (классика — Excel), либо вы пытаетесь писать в системную папку, куда обычному пользователю нельзя.

Ещё три ловушки

  • «Программа ничего не делает». Если запустить python без имени файла, вы попадёте в интерактивный режим — приглашение >>>. Это не зависание, это ожидание команды. Выйти: exit().
  • «Я сохранил файл, но программа видит старую версию». Проверьте, что редактор действительно сохранил (в большинстве редакторов несохранённый файл помечен точкой или звёздочкой у названия вкладки).
  • «Всё пропало после перезагрузки». Значит, данные жили в оперативной памяти и на диск их никто не записывал. Смотрите раздел про стол и шкаф выше.

Практика

Задания идут от простого к сложному. Если Python ещё не установлен, вернитесь к ним после статьи Первая программа — ничего не потеряете.

1. Разминка с битами. Выведите двоичную запись чисел 1, 2, 4, 8, 16 и посмотрите на закономерность. Затем угадайте (сначала на бумаге!), какому обычному числу равно двоичное 1010, и проверьте себя через int("1010", 2).

Подсказка: разряды двоичного числа справа налево — это 1, 2, 4, 8.

2. Своя мини-таблица кодировки. Напечатайте символы с кодами от 65 до 90 через chr(). Что получилось? Затем найдите код своей первой буквы имени через ord().

Подсказка: понадобится цикл for code in range(65, 91):. Циклы мы разбираем в статье Циклы, но здесь можно просто скопировать эту строку и написать под ней с отступом print(code, chr(code)).

3. Символы против байтов. Возьмите строку из пяти русских букв и пяти латинских. Выведите len(строка) и len(строка.encode("utf-8")). Объясните себе разницу вслух. Затем добавьте в строку эмодзи и посмотрите, на сколько выросло число байтов.

Подсказка: ответ должен получиться в духе «латиница по 1 байту, кириллица по 2, эмодзи по 4».

4. Разведка файловой системы. Напишите программу, которая печатает текущую рабочую директорию, список файлов в ней, а для каждого файла — его размер в байтах.

Подсказка: размер даёт os.path.getsize(имя). Пройдитесь циклом по os.listdir("."). Осторожно: в списке могут быть папки, у которых размер означает не то, что вы ожидаете, — это нормально.

5. Файл-счётчик (посложнее). Сделайте программу, которая при каждом запуске увеличивает число в файле counter.txt на единицу и печатает его. При самом первом запуске файла ещё нет — программа должна это пережить и начать с единицы.

Подсказка: проверить наличие файла можно через os.path.exists("counter.txt"). Прочитанное из файла — всегда строка, её нужно превратить в число через int(...), а перед записью обратно — в строку через str(...). Это задание объединяет всё: байты, файлы, типы и обработку ситуации «а если файла нет».


Мини-итог

  • Внутри компьютера только биты — нули и единицы. Восемь бит — байт, 256 возможных значений.
  • Числа, буквы, картинки, звук — всё это байты. Смысл байтам придаёт договорённость о том, как их читать: кодировка для текста, тип данных для чисел, формат для картинки.
  • Оперативная память — быстрый стол, который стирается при выключении. Диск — медленный, но постоянный шкаф. Чем дальше данные от процессора, тем дороже до них добираться.
  • Процессор бесконечно крутит цикл «взять инструкцию — понять — выполнить — записать», миллиарды раз в секунду. Он не умный, он быстрый и буквальный.
  • Файл — это именованная последовательность байтов. Расширение — часть имени, а не свойство содержимого.
  • Путь бывает абсолютным (полный адрес) и относительным (от текущей папки). Большинство ошибок «файл не найден» — про относительные пути.

Если из всей статьи запомнить одну мысль, пусть будет эта: данные не имеют смысла сами по себе, смысл задаёт программа. Дальше всё программирование — про то, как аккуратно задавать этот смысл.

Что почитать и посмотреть


Что дальше

Мы разобрались, как устроена одна машина. Но почти всё интересное сегодня происходит, когда машины разговаривают друг с другом: вы открываете сайт, и байты летят через полмира.

В следующей статье — Как работают интернет и сайты: клиент, сервер, домены, HTTP — посмотрим, что происходит между нажатием Enter в адресной строке и появлением картинки на экране.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов