Ответы пользователя по тегу Операционные системы
  • Почему ОС запускается на QEMU, а на Virtual Box нет?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Почему ОС не запускается на Virtual box, а на QEMU запускаеться?

    Потому что QEMU TCG не очень точно эмулирует x86. Если в командную строку qemu добавить -enable-kvm, то оно тоже не будет работать.

    А вот если добавить ret в реализацию GDT::load_gdt() и не вызывать load_tss() то оно начинает в qemu-kvm работать:
    diff --git a/src/GDT/GDT_impl.s b/src/GDT/GDT_impl.s
    index 4f69835abd9d..5476837f3d0a 100644
    --- a/src/GDT/GDT_impl.s
    +++ b/src/GDT/GDT_impl.s
    @@ -2,6 +2,7 @@
     .extern _ZN8SimpleOS3GDT7gdt_ptrE
     _ZN8SimpleOS3GDT8load_gdtEv:
            lgdt _ZN8SimpleOS3GDT7gdt_ptrE
    +       ret
     
     .global _ZN8SimpleOS3GDT8load_tssEv
     _ZN8SimpleOS3GDT8load_tssEv:

    Подозреваю, что и в virtual box оно заработает.
    Проблема в загрузке TSS, короче. Мне не знакомы дебри TSS, но для поверхностного взгляда кажется странным то, что все сегментные селекторы в твоём TSS имеют RPL=3, а селектор стека -- RPL=0.
    Если ты хочешь разбираться с тем, что происходит, то разумно было бы поменять порядок инициализации, сначала загрузить IDT, чтобы иметь возможность обрабатывать исключения, а потом загружать TSS и смотреть, что за исключение эта операция выкидывает.
    С другой стороны, непонятно зачем в текущей версии этой ОС нужен TSS. Подозреваю, что никакая функциональность не будет потеряна, если его не загружать.
    Ответ написан
    Комментировать
  • При размере страницы, скажем, 4 Кбайт 32-разрядное адресное пространство имеет 1 млн страниц. Верно ли это?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Без всякого деления можно понять, что 2^32 / (4 * 2^10) = 2^(32 - 12) = 2^20, а если запомнить, что 2^10 -- это "двоичная тысяча" (1024), а 2^20 -- соответственно "двоичный миллион" (1024^2), то жизнь заметно упрощается.
    Ответ написан
    Комментировать
  • Почему название файла в моей ОС изменяется само?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Проблема, как я вижу, зарыта в реализации метода map::copy, вот в этом месте. Нельзя копировать сложные объекты функцией memcpy(), потому что при удалении копии её деструктор освободит данные оригинала. Для такого копирования следует либо использовать placement new, либо копировать целиком объект Node, типа того. Для того чтобы это работало потребуется реализовать глобальный оператор new, типа того. С этими изменениями конкретно описанная в топике проблема решается, но я вижу, что и другие методы класса map реализованы с похожими ошибками, так что не расслабляйся и не думай, что это была единственная проблема в коде.

    Отдельный вопрос: зачем FileSystem::get_root() копирует всё дерево файловой системы, когда FileSystem::tree() явно не собирается его менять, можно было бы в этом случае обойтись константной ссылкой.
    Ответ написан
    1 комментарий
  • Не работает таблица прерываний(IDT). Что делать?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    В чем может быть проблема?

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

    Вот эта структура:
    struct IDTPtr {
        uint16_t limit;
        uint32_t base;
    };

    должна быть определена с атрибутом packed, иначе компилятор вставляет паддинг для выравнивания поля base на границу uint32_t, из-за чего в IDT загружается неверный адрес.

    Ну и, вдобавок, нельзя делать iret из середины с++-функции, потому что компилятор организовал в ней кадр стека и вместо возврата iret снимает и интерпретирует мусор из этого кадра:
    001000e6 <SimpleOS::IDT::dividing_by_zero()>:
      1000e6:       55                      push   %ebp
      1000e7:       89 e5                   mov    %esp,%ebp
      1000e9:       53                      push   %ebx
      1000ea:       83 ec 04                sub    $0x4,%esp
      1000ed:       e8 15 01 00 00          call   100207 <__x86.get_pc_thunk.ax>
      1000f2:       05 06 11 00 00          add    $0x1106,%eax
      1000f7:       83 ec 0c                sub    $0xc,%esp
      1000fa:       8d 90 08 fe ff ff       lea    -0x1f8(%eax),%edx
      100100:       52                      push   %edx
      100101:       89 c3                   mov    %eax,%ebx
      100103:       e8 4c 00 00 00          call   100154 <SimpleOS::Terminal::print(char const*)>
      100108:       83 c4 10                add    $0x10,%esp
      10010b:       fa                      cli
      10010c:       cf                      iret
      10010d:       90                      nop
      10010e:       8b 5d fc                mov    -0x4(%ebp),%ebx
      100111:       c9                      leave
      100112:       c3                      ret
    Ответ написан
    9 комментариев
  • Как использовать libstdc на Bare Metal?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Есть по-разному устроенные libc, некоторые из них можно приспособить для работы в ядре. Для начала нужно понимать, что в libc есть часть, которая совсем не зависит от ОС (например все функции mem*() или большинство функций str*()), есть часть, зависимость которой от ОС реализована в терминах других функций libc (например printf() может выделять память и может записывать в файл, но обе эти функции уже реализованы в других местах libc), а есть часть напрямую взаимодействующая с ОС.
    Некоторые libc написаны так, что эта последняя часть чётко отделена (например newlib) и для адаптации к другой ОС или к окружению внутри ядра нужно реализовать небольшое количество интерфейсов (для newlib это будет вариант libgloss).
    Ответ написан
    Комментировать
  • Error: no multiboot header found при загрузке своей ОС, как исправить?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Нужны подробности сборки. Согласно спецификации заголовок multiboot должен быть в пределах 8192 байт от начала, но если в сборку не подсовывать никаких дополнительных ключей и потом пытаться загружать elf-файл, то в нём multiboot header может оказаться дальше 8К от начала:

    $ gcc -m32 boot.s -nodefaultlibs -nostartfiles -Wl,-Tlinker.ld -no-pie -o boot.elf
    $ readelf -S boot.elf
    There are 8 section headers, starting at offset 0x3140:
    
    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .note.gnu.bu[...] NOTE            00100000 001000 000024 00   A  0   0  4
      [ 2] .multiboot        PROGBITS        00101000 003000 00000c 00      0   0 4096
      [ 3] .text             PROGBITS        00102000 002000 000009 00  AX  0   0 4096
      [ 4] .bss              NOBITS          00103000 003000 004000 00  WA  0   0 4096
      [ 5] .symtab           SYMTAB          00000000 00300c 0000a0 10      6   9  4
      [ 6] .strtab           STRTAB          00000000 0030ac 00004d 00      0   0  1
      [ 7] .shstrtab         STRTAB          00000000 0030f9 000044 00      0   0  1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
      L (link order), O (extra OS processing required), G (group), T (TLS),
      C (compressed), x (unknown), o (OS specific), E (exclude),
      D (mbind), p (processor specific)


    При такой сборке секция .multiboot оказалась по смещению 0x3000.
    Ответ написан
    Комментировать
  • Как ос генерируют виртуальные адреса, по которым будет поиск, не физические? Почему программы с одних адресов начинаются?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Почему программы с одних адресов начинаются?

    Потому что часть программ просто линкуется в одни и те же адреса. Это т.н. position-dependent executable, их в принципе невозможно загрузить для исполнения по другому адресу, и до недавнего времени (по крайней мере в linux) это было поведением по умолчанию. Но вот уже лет 10 во многих дистрибутивах по умолчанию используются PIE -- position-independent executables, и если не выключен ASLR, то при каждой загрузке часть адреса выбирается случайно. Но это сделано не для улучшения поведения TLB а для безопасности, поскольку это затрудняет атаки использующие известные адреса загрузки кода.

    Вот есть Tlb, представим это как линейный массив, или c , если все адреса будут одинаковы. То Очевидно что tlb будет работать в 1% своего множества

    Нет, не очевидно. Потому что никто не делает процессоры с пропорциональным отображением всего адресного пространства на все записи TLB. Обычно индексом записи TLB являются младшие биты номера страницы. Т.е. две соседние страницы могут использовать два соседних индекса в TLB, а канал TLB на 256 записей может быть полностью использован всего-то непрерывным мегабайтом памяти в 4-КБайтных страницах.
    Ответ написан
    Комментировать
  • Что планирует ОС - потоки или процессы?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Из всего изученного стало понятно, что процессы - это некие "контейнеры", содержащие id, статус, instruction pointer, значение регистров, открытые файлы и другие данные контекста.

    Вот уже по этому предложению видно, что понятно не стало. instruction pointer и значения регистров -- свои у каждого потока.

    какую роль в планировании играют процессы?

    Никакой.

    Для чего они нужны?

    Для учёта ресурсов и создания изолированных адресных пространств.

    Как планировщик ОС работает с процессами?

    Никак.

    Моя единственная догадка в том, что планировщик как бы "заглядывает" в каждый процесс и уже там работает с потоками.

    Не нужно ему никуда заглядывать. У него есть списки потоков находящихся в разных состояниях, а планирование заключается в перемещении потоков по этим спискам.
    Ответ написан
    9 комментариев
  • Почему не работает загрузчик?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    я так понял, происходит прыжок в никуда?

    Расскажи, как ты это понял?

    почему может не работать?

    Первая ошибка здесь: твой код собран под 0:7c00, но ты загрузил в ds 7c00 вместо нуля.
    Ответ написан
  • Как в архитектурах risc одинаковые виртуальные адреса разных контекстов задач различаются?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    В risc вроде там куча csr регистров

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

    на разных компиляторах одна си операция ассемблируется то в комбинацию lui + addi, то в комбинацию auipc +addi

    Ты указал ARM в тегах, но у ARM нет инструкций lui и auipc. Такие инструкции есть у RISC-V.
    Если ты посмотришь в The RISC-V Instruction Set Manual, раздел 2.4 Integer Computational Instructions, то увидишь, что опкод lui загружает константу собранную из 20 битов непосредственного значения из инструкции и 12 нулевых младших битов в целевой регистр, а auipc прибавляет такую же точно константу к PC и загружает в целевой регистр результат сложения, и в этом вся разница между ними.

    где какая используется не совсем понимаю.

    lui используется для генерации констант, которые не зависят от того, где расположен код, а auipc для генерации констант, которые двигаются вместе с кодом. Т.е. Если ты хочешь вызвать функцию, которая находится дальше чем ±2К от точки вызова, ты можешь сгенерировать её адрес инструкцией auipc, и полученный код будет работать одинаково, независимо от того, по какому адресу он будет размещён. А если тебе надо поместить в регистр константу, например 0x12345678, то ты можешь это сделать парой инструкций lui rd, 0x12345 ; addi rd, rd, 0x678 и значение константы будет всегда одинаковым, вне зависимости от того, где будет этот код.

    Каким образом одинаковые адреса различаются. Или они просто не могут быть одинаковыми(типа ос позаботиться)?

    Если есть MMU и он используется ОС, то есть и виртуальные адреса и они могут быть одинаковыми у разных процессов. Если MMU нет или он не используется, то ОС размещает все процессы в одном адресном пространстве, нет смысла говорить отдельно о виртуальных адресах, поскольку они равны физическим, адреса выделяемые ОС разным процессам могут быть как одинаковыми (например несколько процессов запущенных из одного исполняемого образа могут использовать один и тот же код и константные данные), так и разными (например изменяемые данные разные у всех процессов, а стеки разные у всех потоков).

    Как они в tlb обрабатываются, если вдруг они реально могут быть одинаковыми, и там нету ни каких дополнительных индексов процесса)

    Выбери конкретную процессорную архитектуру -- обсудим. у многих RISC-архитектур есть ASID, который идентифицирует адресное пространство и записывается вместе с виртуальным адресом в TLB. Но в любом случае ASID -- это просто оптимизация для повышения производительности, когда он отсутствует или переполняется ОС должна сбрасывать содержимое TLB при переключении адресного пространства.

    У ARM есть регистры TTBR с примерно той же функцией, что и cr3 в x86.
    У RISC-V есть CSR satp, содержащий ASID и базовый адрес корневого каталога страничных таблиц. Об этом можно прочитать в разделе 4.1.12 Supervisor Address Translation and Protection (satp) Register спецификации The RISC-V Instruction Set Manual Volume II: Privi....
    Ответ написан
    Комментировать
  • Может ли прерывание прервать выполнение конструктора / деструктора в С++?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Может ли прерывание прервать выполнение конструктора / деструктора в С++?

    Да, конечно может, как и любой другой код, явно не защищённый от прерываний.

    нужно ли в конструкторах / деструкторах защищать код критическими секциями?

    Нужно защищать любое состояние, к которому может обращаться код из разных контекстов выполнения -- например из разных потоков или из потока и обработчика прерывания. В ядре linux (где разных типов контекста много и разных способов синхронизации тоже много) есть руководство, что и когда использовать.
    Ответ написан
    Комментировать
  • Как увеличить FPS в системе?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Как увеличить FPS в системе?

    Ты уже задавал этот вопрос в комментариях к предыдущему вопросу, там есть ответ:

    Основной способ стандартный -- не перерисовывать всё, если можно этого избежать.

    Нет нужды перерисовывать весь интерфейс в бэк-буфере, если ничего не изменилось кроме положения мыши. Указатель мыши вообще можно рисовать прямо во фрейм-буфере, когда он перемещается копировать маленький квадратик из бэк-буфера и рисовать стрелку в новом месте.
    Ответ написан
  • Как исправить движение мыши?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    mov al, [bp+12]
      mov bl, al
      mov cl, 3
      shl al, cl
    
      sbb dh, dh
      cbw
      mov dl, [bp+8]
      mov al, [bp+10]

    Этот кусок кода из обработчика перемещений мыши должен делать знаковое расширение 9-битных координат, но я вижу, что в существующем коде в обработчик перемещений всегда приходят нули в координатах x и y, а из-за этого знакового расширения отрицательные dx и dy всегда становятся равными -256. Если добавить вызов функции initialize (0xc205) прерывания 0x15 в mouse.init с 3-байтными пакетами:
    kernel.routine.mouse.init:
      push es bx
      int 0x11
      test ax, 4
      jz kernel.routine.mouse.init.error
      mov ax, 0xC205
      mov bh, 3
      int 0x15
      jc kernel.routine.mouse.init.error
      mov ax, 0xC203
      mov bh, MOUSE_RESOLUTION
      int 0x15
      jc kernel.routine.mouse.init.error

    то у меня всё начинает работать как ожидалось.
    Кстати в ответе на SO по твоей ссылке вызов этой функции есть, непонятно зачем ты его выкинул.
    Ответ написан
    2 комментария
  • Как узнать, откуда прерывание?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    проблема заключается в том, что я не знаю, как получить состояние машины в момент прерывания, чтобы его сохранить.

    Возможно тебе следует выяснить, что это конкретно за "состояние машины" которое ты собираешься сохранить, возможно это поможет тебе его найти.
    Ответ написан
    4 комментария
  • Как лучше это сделать?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    в скрипте линкера есть такая строка . = 0x100000;. Знаю, что grub загружает по этому адресу ядро

    нужно чтобы grub по прежнему загружал ядро по адрсесу 0x100000. А адресация внутри ядра происходила по виртуальному адресу, допустим 0x60000000

    Для начала нужно понять откуда grub берёт адрес для загрузки. Если из образа ядра, то я вижу один возможный вариант решения, если нет -- то два.
    Универсальное решение -- разбить ядро на две части: выполняющуюся сразу после загрузки и до включения виртуальной адресации и выполняющуюся с виртуальной адресацией. Линковать их двумя отдельными командами с разными скриптами линковщика, образ второй части встраивать в первую через какой-нибудь .incbin. Так делает ядро linux. Отсюда можно проследить как это делается.
    Не универсальное решение на случай, когда grub берёт адрес загрузки не из образа ядра, а часть кода работающая до переключения на виртуальную адресацию сравнительно проста -- сделать код этой части независимым от адреса загрузки, а образ ядра линковать для конечного виртуального адреса.
    Ответ написан
    2 комментария
  • Как создать массив хендлов на MASM?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Для полноты картины не хватает определений hBmp, hbtm и hInstance.
    Косяк бросающийся в глаза сразу -- ты создаёшь таймер в обработчике WM_INITDIALOG, но нигде его не удаляешь и он продолжает тикать и после закрытия диалога.
    Ответ написан
    Комментировать
  • Что происходит во время прерывания в многопоточной среде? Или прерывания посреди прерывания?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    откуда прерывания знают адрес, куда им вернуть результат.

    Прерывания не знают. Это работа ОС -- организовать информацию так, чтобы при получении прерывания разобраться, откуда оно, зачем, и что с ним делать.

    Прерывание в коде int 21h и прочие Это какое?

    Любое прерывание инициируемое инструкцией int -- программное. В отличие от прерывания, инициируемого уровнем или фронтом сигнала подключённого к контроллеру прерываний.

    Вообще, кажется, если понять, что программные прерывания на x86 используются просто как удобный способ вызывать функции ядра, т.е. делать системные вызовы, то путаницы должно стать меньше. Не думай о них как о прерываниях, думай о них просто как о вызовах функций ядра. Ядро делает всё для того, чтобы для приложения это именно так и выглядело.

    выполнение прерывания std::cin>>line; Может и сутки длиться.

    Системного вызова. std::cin >>line в конце концов превращается в системный вызов read.

    Вот как по пунктам для этой команды. (С условием что есть еще 1 поток жаждущий ЦП).

    Можно запустить такую программу, узнать её PID, выполнить где-нибудь cat /proc/<PID>/stack и увидеть следующую картину:
    [<0>] wait_woken+0x67/0x80
    [<0>] n_tty_read+0x426/0x5a0
    [<0>] tty_read+0x135/0x240
    [<0>] new_sync_read+0x115/0x1a0
    [<0>] vfs_read+0xf4/0x180
    [<0>] ksys_read+0x5f/0xe0
    [<0>] do_syscall_64+0x33/0x80
    [<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9

    Т.е. поток выполнил системный вызов (ksys_read), зашёл в VFS, добрался до функции чтения в драйвере терминала (tty_read) и перешёл в состояние ожидания в глубине этой функции (wait_woken). Когда драйвер терминала получит данные для программы, он разбудит её поток и системный вызов завершится. До тех пор этот поток будет не готов к выполнению и скедулер ОС просто не будет выделять ему время.

    Мне кажется, что тебе было бы полезно прочитать вторую и третью книжки (Understanding the Linux Kernel и Linux Kernel Developmen) из моего списка.
    Ответ написан
    Комментировать
  • Как происходит доступ к эл. массива на уровне ядра? Malloc выделяет непрерывную физическую память?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Как происходит доступ к эл. массива на уровне ядра?

    Так же как и на уровне приложения -- через трансляцию виртуального адреса в физический.

    Например массив Int* arr = new int[1024*1024*1024] он как храниться?

    Если мы для определённости возьмём linux, то у ядра есть несколько разных способов выделения памяти, в зависимости от того, для чего эта память выделяется. Есть наиболее простой и стандартный kmalloc который выделяет память непрерывную как виртуально так и физически. Обычно этим механизмом нельзя выделить большой непрерывный кусок. Есть vmalloc, который выделяет непрерывную виртуально, но возможно прерывную физически память. Есть get_free_pages который выделяет непрерывные страницы физической памяти, возможно, не отображаемые ни в какие виртуальные адреса. Есть Contiguous Memory Allocator который при старте системы резервирует кусок непрерывной физической памяти и может аллоцировать оттуда куски по запросу.
    Важный момент состоит в том, что аллокации делаемые ядром linux через упомянутые интерфейсы всегда обеспечиваются физической памятью, у памяти ядра нет пейджинга.

    А физическая, для массива то же? Ведь, так будет доступ намного быстрее?

    Почему быстрее? С точки зрения процессора всё равно будет трансляция виртуального адреса в физический, если повезёт -- попадание в TLB, если не повезёт -- ходить по каталогам и таблицам страниц в памяти.

    получается эмулятор каждый адрес вычислять что ли?

    Простой эмулятор -- да, наверно. Умный эмулятор может кешировать эту информацию, например именно это свойство даёт QEMU большую часть его Q.
    Ответ написан
    Комментировать
  • Почему не работает клавиатура?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    почему-то не работает прерывание 0х20 для клавиатуры

    я не буду отлаживать твой код, но предложу тебе несколько вопросов, которые помогут тебе посмотреть в нужные места:
    - посылает ли клавиатура вообще IRQ?
    - настроен ли контроллер прерываний на приём IRQ от клавиатуры?
    - размаскирована ли линия IRQ от клавиатуры в контроллере прерываний?
    - настроен ли контроллер прерываний на то чтобы вызывать int 0x20 в ответ на IRQ от клавиатуры? [1]
    - получает ли процессор прерывание от контроллера прерываний?
    - настроена ли запись в IDT для int 0x20 на вызов твоего обработчика?
    - разрешены ли прерывания в процессоре?

    [1] Коль скоро здесь ты установил базовый вектор первого PIC в 0x20, то клавиатура, посылая IRQ1, должна вызывать int 0x21.
    Ответ написан
    Комментировать
  • Почему Qemu вылетает?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Я потратил несколько дней на то, чтобы понять в чем дело. Ничего не вышло.

    Эта часть вопроса не даёт нам никакой полезной информации. Рассказывай что делал, а мы попробуем понять, что пошло не так в твоём расследовании.
    Ответ написан