Как в драйвере (kernel mode) организовать передачу данных из устройства непосредственно в буфер процесса (user mode), минуя буфер в драйвере?
ОС Linux 2.4.18.
PCI устройство в режиме мастеринга (без участия процессора, как в DMA) заливает данные в буфер памяти. Адрес начала буфера и его размер я пишу в порты в\в устройства.
Хотелось бы передавать устройству физический адрес буфера процесса запросившего данные.
Объемы данных (несколько аудио и видео потоков) большие и поэтому лишнее копирование из устройства в буфер драйвера и оттуда в буфер процесса крайне не желательно. В то же время использовать для перекачки данных процессор, когда драйвер читает данные из порта и передаёт их процессу ( что-то типа put_user(inb(PORT), buf++); ) то же достаточно накладно.
Как можно реализовать схему при которой данные из устройства сразу поступают в адресное пространство процесса ждущего данные?
Что-то типа следующего:
Процесс получает буфер (malloc) и передаёт указатель на него и его размер в драйвер (при помощи ioctl). Процесс засыпает в ioctl (при помощи очереди ожидания). В драйвере определяется физический адрес этого буфера и передаётся устройству. Устройство заполняет буфер данными и генерирует прерывание. В обработчике прерывания процесс пробуждается и при выходе из ioctl буфер либо заполнен либо возвращается код ошибки.
Здравствуйте, unkn2000, Вы писали:
U>Как в драйвере (kernel mode) организовать передачу данных из устройства непосредственно в буфер процесса (user mode), минуя буфер в драйвере?
U>ОС Linux 2.4.18. U>PCI устройство в режиме мастеринга (без участия процессора, как в DMA) заливает данные в буфер памяти. Адрес начала буфера и его размер я пишу в порты в\в устройства. U>Хотелось бы передавать устройству физический адрес буфера процесса запросившего данные. U>Объемы данных (несколько аудио и видео потоков) большие и поэтому лишнее копирование из устройства в буфер драйвера и оттуда в буфер процесса крайне не желательно. В то же время использовать для перекачки данных процессор, когда драйвер читает данные из порта и передаёт их процессу ( что-то типа put_user(inb(PORT), buf++); ) то же достаточно накладно. U>Как можно реализовать схему при которой данные из устройства сразу поступают в адресное пространство процесса ждущего данные? U>Что-то типа следующего: U>Процесс получает буфер (malloc) и передаёт указатель на него и его размер в драйвер (при помощи ioctl). Процесс засыпает в ioctl (при помощи очереди ожидания). В драйвере определяется физический адрес этого буфера и передаётся устройству. Устройство заполняет буфер данными и генерирует прерывание. В обработчике прерывания процесс пробуждается и при выходе из ioctl буфер либо заполнен либо возвращается код ошибки.
U>Подскажите хоть в каком направлении рыть.
Здравствуйте, unkn2000, Вы писали:
U>Здравствуйте, Murr, Вы писали:
M>>Пользуйтесь kio (direct_IO) интерфейсом.
U>А чуть подробней можно? U>Что это за интерфейс такой? U>Где про него почитать можно? U>Хотя бы в какие файлы заглянуть?
Почитать, честно говоря, не знаю где. Может в LDP что-нибудь и есть...
Посмотреть можно
mm/filemap.c: generic_file_read, generic_file_direct_IO.
Ядро само заполняет kiobuf ссылками на страницы переданного userspace буфера, т.е. не нужно самостоятельно делать им pin и трансляцию.
Здравствуйте, Murr, Вы писали:
M>Здравствуйте, unkn2000, Вы писали:
U>>Здравствуйте, Murr, Вы писали:
M>>>Пользуйтесь kio (direct_IO) интерфейсом.
U>>А чуть подробней можно? U>>Что это за интерфейс такой? U>>Где про него почитать можно? U>>Хотя бы в какие файлы заглянуть?
M>Почитать, честно говоря, не знаю где. Может в LDP что-нибудь и есть... M>Посмотреть можно M>mm/filemap.c: generic_file_read, generic_file_direct_IO. M>Ядро само заполняет kiobuf ссылками на страницы переданного userspace буфера, т.е. не нужно самостоятельно делать им pin и трансляцию.
Просто в struct file нужно определить одну доп операцию — direct_IO.
И из user-space открывать dev с флажком O_DIRECT.
U>Мне ответили, но что-то не получается. Не могли бы вы помоч разобраться.
Как работает драйвер блочного устройства?
Вы регистрируете устройство, при открытии inode передается управление в вашу функцию open.
Вы определяете для переданной inode операции a_ops (inode->i_mapping->a_ops), конкретнее — direct_IO.
Если Ваше устройство открывается в режиме O_DIRECT, то при чтении используется callback direct_IO.
В качестве примера реализации для блочного ввода-вывода просто посмотрите fs/buffer.c:generic_direct_IO.
То есть фактически Вам на вход подается массив pinned страниц — вам только остается посчитать физический адрес каждой страницы и подать на устройство. Чтобы получить физический адрес скорее всего достаточно воспользоваться макросом page_to_phys (вроде бы должно работать даже на NUMA/DISCONTIG).
P.S. Попробуйте написать Если есть какие-то вопросы — я отвечу в меру своих знаний.
Аноним довольно профессионально описал как это сделать с mmap, но там есть ряд своих тонкостей.
Например, если не пишешь свои address_space operations, то нужно объявлять VMA как VM_RESERVED или VM_LOCKED, чтобы vmscan/kswapd не отправил страницы в swap .
В этом отношении direct_IO попроще и именно поэтому я его и посоветовал.
P.S. Правда, в случае с direct_IO могут возникнуть интересные эффекты, если страница будет выделена выше 4 Гб (на PC)... Не знаю могут ли Bus Master устройства так высоко залезать.
P.S. Пример с балды, поэтому могут быть неточности... в принципе было достаточно выводить физический адрес страницы, но я для пример заполнял передаваемый буфер буквами Q.
Здравствуйте, Murr, Вы писали:
M>Ну да, после memset надо бы еще flush_dcache_page делать
M>Ну в общем, есть там неточности M>но в целом вроде пример почти рабочий
M>Во всяком случае из него видно что и как )
Но только первая страница выводиться с ‘Q’, затем ‘A’.
Маловато будет.
А фун. flush_dcache_page – на результат не влияет.
Ещё Вы писали: >P.S. Правда, в случае с direct_IO могут возникнуть интересные эффекты, если >страница будет выделена выше 4 Гб (на PC)... Не знаю могут ли Bus Master устройства >так высоко залезать.
Здравствуйте, unkn2000, Вы писали:
U>Но только первая страница выводиться с ‘Q’, затем ‘A’. U>Маловато будет.
Да нет... все эти memset — это просто так...
Дальше printk, который выводит физический адрес страницы, смысла смотреть нет.
Это так — народное творчество
U>А фун. flush_dcache_page – на результат не влияет.
Здравствуйте, Murr, Вы писали:
M>Здравствуйте, unkn2000, Вы писали:
U>>Но только первая страница выводиться с ‘Q’, затем ‘A’. U>>Маловато будет. M>Да нет... все эти memset — это просто так... M>Дальше printk, который выводит физический адрес страницы, смысла смотреть нет. M>Это так — народное творчество
U>>А фун. flush_dcache_page – на результат не влияет.
M>На PC — да.
U>>В любом случае спасибо за помощь.
M>Не за что... просто имейте в виду этот вариант.
Я понимаю, но всё же, что б закончить с примером надо исправить.
memset (arr, 'A', ARRAY_SIZE);
pread (fd, arr, ARRAY_SIZE, 0);
//в место pread (fd, arr+getpagesize(), getpagesize(), 0);
//или того, что я написал в начале pread (fd, arr, getpagesize(), 0);
//и в первом и во втором случае отображалось только первая страница
close (fd);
write (1, arr, ARRAY_SIZE);
Правильно ли я понял смысл этого метода?
В user mode выделяем память кратную размеру страницы памяти (для Intel это обычно 4КБ)
Кстати, а память выделяется не прерывным куском?
При открытии устройства сообщаем ядру, что используем O_DIRECT.
Драйвер совместно с ядром подцепляют fun_direct_IO.
Далее все запросы на чтение \ запмись передаются этой функции.
В fun_direct_IO мы последовательно отображаем (kmap) и инициализируем каждую страницу.
А где эти страницы располагаются физически?
Я так понял они не свопируются и реально сидят в памяти. А значит, устройство может к ним адресоваться.
Или я чего-то не понимаю?
Murr растолкуй пожалуйста.
В принципе если бы выполнялось два условия:
1. Страницы располагагись не прерывно в памяти.
2. И фун. page_to_phys(первая страница) возвращала бы физический адрес до 4GB.
Или хотя бы второе то этот вариант можно было бы попробовать использовать в моём случае.
Я бы
1. в fun_direct_IO отображал страницу, передовал адрес устройству,
2. усыплял процесс в fun_direct_IO,
2. устройство писало данные,
3. по прерыванию будил процесс, kunmap
4. У процесса свободный не кем не занитый буфер.
и т.д. в цикле в принцепе если сами буфферы обрабатывать в другом потоке,
то первый поток в полне бы успевал снабжать устройство новыми буфферами.
Здравствуйте, unkn2000, Вы писали:
U>Я понимаю, но всё же, что б закончить с примером надо исправить. U> memset (arr, 'A', ARRAY_SIZE); U> pread (fd, arr, ARRAY_SIZE, 0); U> //в место pread (fd, arr+getpagesize(), getpagesize(), 0); U> //или того, что я написал в начале pread (fd, arr, getpagesize(), 0); U> //и в первом и во втором случае отображалось только первая страница U> close (fd); U> write (1, arr, ARRAY_SIZE);
Се-то я не понимаю.
Я специально заполняю только маленький кусочек и в середине выделенного буфера, чтобы показать, что корректно обрабатываю передаваемый в kernel-space буфер, т.е. то, что нет переполнения ни вперед ни назад. Зачем мне заполнять от начала или до конца?
U>Правильно ли я понял смысл этого метода? U>В user mode выделяем память кратную размеру страницы памяти (для Intel это обычно 4КБ) U>Кстати, а память выделяется не прерывным куском?
Нет.
U>При открытии устройства сообщаем ядру, что используем O_DIRECT. U>Драйвер совместно с ядром подцепляют fun_direct_IO. U>Далее все запросы на чтение \ запмись передаются этой функции.
Да.
U>В fun_direct_IO мы последовательно отображаем (kmap) и инициализируем каждую страницу.
Я прямо начинаю сожалеть, что вообще сделал побочный эффект в direct_IO.
kmap и подобная хрень для Ваших целей вообще не нужна! Всё, что помечено "side effect" — это просто побочный эффект. kmap нужен чтобы из ядра обращаться к соответствующей странице (чтобы она получила в нем трансляцию), но Вам для ввода-вывода это нафиг не нужно. Просто я не хотел писать драйвер устройства, который осуществляет реальный ввод-вывод, поэтому ограничился простым заполнением буфера (а для этого потребовалось спроецировать страницу на ядро) как демонстрацией побочного эффекта.
U>А где эти страницы располагаются физически? U>Я так понял они не свопируются и реально сидят в памяти. А значит, устройство может к ним адресоваться.
Располагаются где угодно. Верно.
U>В принципе если бы выполнялось два условия: U>1. Страницы располагагись не прерывно в памяти.
Довольно странное требование для I/O, IMHO. Это, как я понимаю, было бы удобно, но вообще-то необязательно.
U>2. И фун. page_to_phys(первая страница) возвращала бы физический адрес до 4GB.
Вот это можно гарантировать только на машинах с < 4 Гб, поскольку буфера пользователя могут быть выделены где угодно (GFP_USER).
U>Я бы U>1. в fun_direct_IO отображал страницу, передовал адрес устройству,
Не нужно отображать страницу! Она уже pinned, а физический адрес Вы можете получить с помощью макроса.
U>2. усыплял процесс в fun_direct_IO,
Угу.
U>2. устройство писало данные,
Угу.
U>3. по прерыванию будил процесс, kunmap
kunmap не нужен.
U>4. У процесса свободный не кем не занитый буфер. U>и т.д. в цикле в принцепе если сами буфферы обрабатывать в другом потоке, U>то первый поток в полне бы успевал снабжать устройство новыми буфферами.
Ничего не понял насчет "потоков".
Зачем нужны какие-то "потоки"?
Я это так представляю: в direct_IO Вы программируете устройство на передачу данных с гранулярностью PAGE_SIZE, для каждого запроса создаете некую структуру (скажем, в хэшированном списке) со своей очередью ожидания, в прерывании выставляете в этой структуре флажок (успешность/неуспешность операции) и будите всех кто на этой структуре спит, в direct_IO делаете следующий запрос и засыпаете на нем.
Если устройство не поддерживает вложенные транзакции, то схема вообще тривиальная.
Драйвер просто синхронно делает запросы и ожидает ввода/вывода на одной единственной структуре, сериализуя доступ с помощью семафора. И семафор и структура глобальны для драйвера.
Можешь подкинуть простой пример как драйверы пишутся под Linux....
меня интересует когда используется одновременно и бинарник (сам драйвер) и библиотека *.so
(сорри если не совсем корректно изложился)
Спасибо
Здравствуйте, Murr, Вы писали:
M>Се-то я не понимаю. M>Я специально заполняю только маленький кусочек и в середине выделенного буфера, чтобы показать, что корректно обрабатываю передаваемый в kernel-space буфер, т.е. то, что нет переполнения ни вперед ни назад. Зачем мне заполнять от начала или до конца?
А-а-а.… Понял. Я просто не знал, что у Вас на выходе должно быть от этого и мои предложения.
U>>Кстати, а память выделяется не прерывным куском? M>Нет.
Жаль. Просто наш разработчик этого не любит. Хотя с ним мы уже договорились.
Я ему буду передавать адрес массива указателей на начало страниц.
А он мне прерываниями сообщать, когда исчерпает память.
Ну, это примерно.
U>>В fun_direct_IO мы последовательно отображаем (kmap) и инициализируем каждую страницу. M>Я прямо начинаю сожалеть, что вообще сделал побочный эффект в direct_IO. M>kmap и подобная хрень для Ваших целей вообще не нужна! Всё, что помечено "side effect" — это просто побочный эффект. kmap нужен чтобы из ядра обращаться к соответствующей странице (чтобы она получила в нем трансляцию), но Вам для ввода-вывода это нафиг не нужно. Просто я не хотел писать драйвер устройства, который осуществляет реальный ввод-вывод, поэтому ограничился простым заполнением буфера (а для этого потребовалось спроецировать страницу на ядро) как демонстрацией побочного эффекта.
Всё я, кажется, понял мне достаточно сделать что-то типа:
Обвить void *g_addr = NULL;
В функции fun_direct_io(…) {… g_addr = page_to_phys(pg);
g_addr отдаём устройству.
Кажется, теперь правильно?
U>>А где эти страницы располагаются физически? U>>Я так понял они не свопируются и реально сидят в памяти. А значит, устройство может к ним адресоваться. M>Располагаются где угодно. Верно.
Где угодно в физической памяти (на борту в RAM)?
Как я понял у интела страничная (4KB) организация памяти. И есть там ещё какой-то механизм копирование при чтении\записи. На третьем кольце процессам выделяется виртуальная память. Реально ей физическая память выделяется только при обращении и то только той странице, которой принадлежит адрес. НО когда память (список страничек) оказывается драйвере в fun_direct_io она уже реально отображена в RAMе.
Т.е. если у меня на борту 256MB то адреса этих страничек должны быть в диапазоне от 0x0 до 0x100000000 (256MB). И устройство запросто может к ним адресоваться. Всё ли верно?
А что будет, если нет свободных страничек в системе и нет возможности отобразить их в RAM? Я предполагаю, что в этом случае система заблокирует процесс пока память не появиться? Правильно ли я предполагаю?
Еще я не понимаю. Когда память находится в ядре в фун. fun_direct_io (и тогда да же пока процесс в ней спит), память отображена на физические адреса и не свопируется.
Но когда система её снова может свопировать? Мне это нужно, что бы, когда память передаётся процессу, она могла при необходимости менеджером памяти отправляться в своп и не висеть мёртвым грузом в RAM. Нужны ли какие-то дополнительно манипуляции с памятью (например, munlock(…) )? Или только освобождать память?
U>>1. Страницы располагались ни прерывно в памяти. M>Довольно странное требование для I/O, IMHO. Это, как я понимаю, было бы удобно, но вообще-то необязательно.
Согласен. И даже убедил в этом нашего разработчика железа.
U>>2. И фун. page_to_phys(первая страница) возвращала бы физический адрес до 4GB. M>Вот это можно гарантировать только на машинах с < 4 Гб, поскольку буфера пользователя могут быть выделены где угодно (GFP_USER).
Честно говоря, я таких машин даже не видел. Ходя в будущем всё может быть (Хотя там 64 разрядные процессоры и шины на подходе). Ну да буду решать проблемы по мере поступления.
Murr спасибо за помощь.
Приятно было пообщаться.
Видно, что в Линухе ты силён.
Не поделишься источниками информации?
Здравствуйте, Murr, Вы писали:
M>Се-то я не понимаю. M>Я специально заполняю только маленький кусочек и в середине выделенного буфера, чтобы показать, что корректно обрабатываю передаваемый в kernel-space буфер, т.е. то, что нет переполнения ни вперед ни назад. Зачем мне заполнять от начала или до конца?
А-а-а.… Понял. Я просто не знал, что у Вас на выходе должно быть от этого и мои предложения.
U>>Кстати, а память выделяется не прерывным куском? M>Нет.
Жаль. Просто наш разработчик этого не любит. Хотя с ним мы уже договорились.
Я ему буду передавать адрес массива указателей на начало страниц.
А он мне прерываниями сообщать, когда исчерпает память.
Ну, это примерно.
U>>В fun_direct_IO мы последовательно отображаем (kmap) и инициализируем каждую страницу. M>Я прямо начинаю сожалеть, что вообще сделал побочный эффект в direct_IO. M>kmap и подобная хрень для Ваших целей вообще не нужна! Всё, что помечено "side effect" — это просто побочный эффект. kmap нужен чтобы из ядра обращаться к соответствующей странице (чтобы она получила в нем трансляцию), но Вам для ввода-вывода это нафиг не нужно. Просто я не хотел писать драйвер устройства, который осуществляет реальный ввод-вывод, поэтому ограничился простым заполнением буфера (а для этого потребовалось спроецировать страницу на ядро) как демонстрацией побочного эффекта.
Всё я, кажется, понял мне достаточно сделать что-то типа:
Обвить void *g_addr = NULL;
В функции fun_direct_io(…) {… g_addr = page_to_phys(pg);
g_addr отдаём устройству.
Кажется, теперь правильно?
U>>А где эти страницы располагаются физически? U>>Я так понял они не свопируются и реально сидят в памяти. А значит, устройство может к ним адресоваться. M>Располагаются где угодно. Верно.
Где угодно в физической памяти (на борту в RAM)?
Как я понял у интела страничная (4KB) организация памяти. И есть там ещё какой-то механизм копирование при чтении\записи. На третьем кольце процессам выделяется виртуальная память. Реально ей физическая память выделяется только при обращении и то только той странице, которой принадлежит адрес. НО когда память (список страничек) оказывается драйвере в fun_direct_io она уже реально отображена в RAMе.
Т.е. если у меня на борту 256MB то адреса этих страничек должны быть в диапазоне от 0x0 до 0x100000000 (256MB). И устройство запросто может к ним адресоваться. Всё ли верно?
А что будет, если нет свободных страничек в системе и нет возможности отобразить их в RAM? Я предполагаю, что в этом случае система заблокирует процесс пока память не появиться? Правильно ли я предполагаю?
Еще я не понимаю. Когда память находится в ядре в фун. fun_direct_io (и тогда да же пока процесс в ней спит), память отображена на физические адреса и не свопируется.
Но когда система её снова может свопировать? Мне это нужно, что бы, когда память передаётся процессу, она могла при необходимости менеджером памяти отправляться в своп и не висеть мёртвым грузом в RAM. Нужны ли какие-то дополнительно манипуляции с памятью (например, munlock(…) )? Или только освобождать память?
U>>1. Страницы располагались ни прерывно в памяти. M>Довольно странное требование для I/O, IMHO. Это, как я понимаю, было бы удобно, но вообще-то необязательно.
Согласен. И даже убедил в этом нашего разработчика железа.
U>>2. И фун. page_to_phys(первая страница) возвращала бы физический адрес до 4GB. M>Вот это можно гарантировать только на машинах с < 4 Гб, поскольку буфера пользователя могут быть выделены где угодно (GFP_USER).
Честно говоря, я таких машин даже не видел. Ходя в будущем всё может быть (Хотя там 64 разрядные процессоры и шины на подходе). Ну да буду решать проблемы по мере поступления.
Murr спасибо за помощь.
Приятно было пообщаться.
Видно, что в Линухе Вы сильны.
Не поделишься источниками информации?
Здравствуйте, unkn2000, Вы писали:
U>Всё я, кажется, понял мне достаточно сделать что-то типа: U>Обвить void *g_addr = NULL; U>В функции fun_direct_io(…) {… g_addr = page_to_phys(pg); U>g_addr отдаём устройству. U>Кажется, теперь правильно?
Ну насколько я понял задачу — да.
U>На третьем кольце процессам выделяется виртуальная память. Реально ей физическая память выделяется только при обращении и то только той странице, которой принадлежит адрес. НО когда память (список страничек) оказывается драйвере в fun_direct_io она уже реально отображена в RAMе.
Да, Вы абсолютно правы. Все именно так. При вызове direct_IO (в отличие от write) VFS делает много полезных вещей (в принципе их можно делать и самому во write, но удобнее положиться на VFS): для всего диапазона буфера выделяет память(как Вы заметили — она может быть не не выделена) и закрепляет страницы в памяти на время выполнения Вашей функции direct_IO.
U>Т.е. если у меня на борту 256MB то адреса этих страничек должны быть в диапазоне от 0x0 до 0x100000000 (256MB). И устройство запросто может к ним адресоваться. Всё ли верно?
Немного не так. Процессор для запросов на чтение/запись памяти выставляет на своей адресной шине некий адрес — это шинный адрес или физический адрес. Далее некое устройство на этой же шине может порулить запрос еще дальше. На PC этим обычно занимается чипсет для которого определены таблицы "маршрутизации" — каждому шинному адресу соответствует свое устройство, в частности это может быть контроллер памяти. Обычно вся физическая память отображается чипсетом на несколько "окон" шинных адресов. Как именно — можно увидеть из вывода cat /proc/iomem.
Так исторически получилось, что на PC/XT в адресное пр-во размером 1 Мб должны были влезть и ОЗУ и видео-память и ПЗУ, поэтому в целях совместимости сейчас так получается, что физическая память "порезана" на окошки, окно в 640 кб — в начале физических адресов, потом часть пропущена, потом еще окна с физической памятью и т.д.
U>А что будет, если нет свободных страничек в системе и нет возможности отобразить их в RAM? Я предполагаю, что в этом случае система заблокирует процесс пока память не появиться? Правильно ли я предполагаю?
write выйдет с ошибкой ENOMEM, насколько я понимаю.
U>Еще я не понимаю. Когда память находится в ядре в фун. fun_direct_io (и тогда да же пока процесс в ней спит), память отображена на физические адреса и не свопируется. U>Но когда система её снова может свопировать? Мне это нужно, что бы, когда память передаётся процессу, она могла при необходимости менеджером памяти отправляться в своп и не висеть мёртвым грузом в RAM. Нужны ли какие-то дополнительно манипуляции с памятью (например, munlock(…) )? Или только освобождать память?
Со страницами, переданными в direct_IO, ничего особенного делать не надо.
До вызова direct_IO VFS закрепляет (pin) страницы в памяти, по возвращению — делает unpin, т.е. делает возможным выгрузку в swap.
U>Не поделишься источниками информации?
Только исходный код, если честно.
С непривычки его, наверное, читать тяжело, но это проходит — было бы желание.
Ценность — в разы и десятки раз больше, чем чтение каких-либо книжек.
Здравствуйте, Murr, Вы писали:
U>>Т.е. если у меня на борту 256MB то адреса этих страничек должны быть в диапазоне от 0x0 до 0x100000000 (256MB). И устройство запросто может к ним адресоваться. Всё ли верно?
M>Немного не так. Процессор для запросов на чтение/запись памяти выставляет на своей адресной шине некий адрес — это шинный адрес или физический адрес. Далее некое устройство на этой же шине может порулить запрос еще дальше. На PC этим обычно занимается чипсет для которого определены таблицы "маршрутизации" — каждому шинному адресу соответствует свое устройство, в частности это может быть контроллер памяти. Обычно вся физическая память отображается чипсетом на несколько "окон" шинных адресов. Как именно — можно увидеть из вывода cat /proc/iomem.
M>Так исторически получилось, что на PC/XT в адресное пр-во размером 1 Мб должны были влезть и ОЗУ и видео-память и ПЗУ, поэтому в целях совместимости сейчас так получается, что физическая память "порезана" на окошки, окно в 640 кб — в начале физических адресов, потом часть пропущена, потом еще окна с физической памятью и т.д.
А-а-а ну да. Понял. Согласен.
Но все же да же если все устройства (ПЗУ, видеопамять, ...) получат физические адреса перед RAM (но в принципе они всё равно отображаются (накладываются) на RAM), то всё равно адреса RAM не выйдут на моей системе(256MB) за пределы 1 ГB.
Но проблему я понял. В будущем буду иметь ввиду.
M>С Новым Годом!