Дескрипторы сегментов
Имеется дескриптор сегмента, используемый для описания каждого сегмента в системе. Имеются обычные и системные дескрипторы. Ниже представлен дескриптор во всей своей красоте. Такой странный формат создан специально для того, чтобы обеспечить совместимость с 286. Заметьте, что он занимает 8 байт
63-54 55 54 53 52 51-48 47 46 45 44-40 39-16 15-0 Base G D R U Limit P DPL S TYPE Segmet Base Segmet Limit 31-24 19-16 23-0 15-0
Разъяснения:
R | зарезервирован (0) |
DPL | 0 означает ядро, 3 означает пользователя |
G | 1 означает гарантировано 4К (В Linux установлен всегда) |
D | 1 означает 32-х битное значение операнда по умолчанию |
U | определяется программистом |
P | 1 означает присутствие в физической памяти |
S | 0 означает системный сегмент, 1 означает обычный сегмент кода или данных |
TYPE | Существует много возможностей. Прерывания различны для системных и обычных дескрипторов. |
Системные дескрипторы в Linux
TSS: P=1,DPL=0,S=0, type=9, limit=231 пространство для 1 tss_struct
LDT: P=1,DPL=0,S=1, type=2, limit=23 пространство для 3 дескрипторов сегментов
База устанавливается при выполнении fork(). Для каждой задачи есть свой TSS и LDT.
Обычные дескрипторы ядра в Linux (head.S):
код: P=1,DPL=0,S=1,G=1,D=1, type=a, base=0xc0000000, limit=0x3ffff
данные: P=1,DPL=0,S=1,G=1,D=1, type=2, base=0xc0000000, limit=0x3ffff
Состав LDT для task[0] (sched.h):
код: P=1,DPL=3,S=1,G=1,D=1, type=a, base=0xc0000000, limit=0x9f
данные: P=1,DPL=3,S=1,G=1,D=1, type=a, base=0xc0000000, limit=0x9f
LDT, устанавливаемые по умолчанию для оставшихся задач (exec()):
код: P=1,DPL=3,S=1,G=1,D=1, type=a, base=0, limit=0xbffff
данные: P=1,DPL=3,S=1,G=1,D=1, type=2, base=0, limit=0xbffff
Размер сегментов ядра 0х40000 страниц (4КВ страниц с G=1=1GB) тип подразумевает, что для кодового сегмента разрешается read-exec, а для сегмента данных read-write.
Регистры, объединенные посредством сегментации.
Формат сегментного регистра: (для программиста видимым является только селектор)
16 -бит 32-бит 32-бит селектор база физического адреса ограничения сегмента атрибуты
Невидимую часть сегментного регистра более удобно рассматривать в формате, используемом во входах таблицы дескрипторов, которые устанавливает программист. Таблицы дескрипторов имеют связанные с ними регистры, которые используются при их размещении в памяти. GDTR (и IDTR) инициализируются при запуске и вместе с этим определяются таблицы. LDTR загружается при каждом переключении задачи.
Формат GDTR (иIDTR):
32-бит 16-бит Линейный базовый адрес ограничение таблицы
TR и LDTR загружаются из GDT и таким образом имеют формат других сегментных регистров. Регистр задач (TR) содержит дескриптор для TSS текущей выполняемой задачи. Выполнение перехода на селектор TSS вызывает сохранение состояния в старом TSS, в TR загружается новый дескриптор и регистры перезагружаются новым TSS. Эти действия реализуются программой-шедуллером при переключении задач пользователя. Отметим, что поле tss_struct.ltd содержит селектор для LDT этой задачи. Он используется для того, чтобы загрузить LDTR. (sched.h)
Дpайвеpы для блочных устpойств.
Пpи поддеpжке файловой системы устpойства, она должна быть pазбита на блоки самим устpойством. Это означает что устpойство не должно пpинимать инфоpмацию посимвольно, а значит должно быть pавнодоступно. Иными словами вы, в любой момент вpемени должны имеет доступ к любому состоянию физического устpойства.
Вам не пpидется в случае блочных устpойств пользоваться функциями read() и write(). Вместо них используются функции block_read() и block_write() находящиеся в VFS и называемые !strategy routine! или функцию request() котоpую вы пишете в позиции функций read() и write() в вашем дpайвеpе. strategy routine вызывается также механизмом кэшиpования буфеpа, котоpый запускается подпpогpаммами VFS, котоpые пpедставлены в виде обычных файлов.
Запpосы ввода-вывода поступают чеpез механизм кэшиpования буффеpа в подпpогpамму называется ll_rw_block, котоpая создает список запpосов упоpядоченных алгоpитмом !elevator!, котоpый соpтиpует списки для более быстpого доступа и повышения эффективности pаботы устpойств.
Затем она вызывает фнкцию request() для осуществления ввода - вывода. Отметим что диски SCSI и CDROM также относятся к блочным устpойствам но упpавляются более особым обpазом. Часть 2.7 "Hаписание дpайвеpа SCSI" описывает это более подpобно.
Физическая память
Ниже представлена карта физической памяти перед тем, как будет выполнен любой процесс. Левый столбец представляет начальный адрес инструкции, отмеченные значения являются приблизительными. Средний столбец включает в себя название инструкции. Крайний правый столбец представляет имя соответствующей процедуры или переменной или комментарий входа.
0x110000 свободна memory_end или high_memory
mem_map mem_init()
inode_table inode_init()
ннформ. устройства device_init()+
0x100000 добав. pg_tables paging_init()
0x0A0000 не используется
0x060000 свободна
low_memory_start
0x006000 код ядра + данные
floppy_track_buffer
bad_pg_table занято page_fault_handlers для bad_page уничтожения процесса, если он находится вне памяти.
0x002000 pg0 первая таблица страниц в ядре
0x001000 swapper_pg_dir каталог страниц ядра
0x000000 нулевая страница
+ устройство, захватывающее память (main.c): profil_buffer, con_init, psaux_init, rd_init, scsi_dev_init. Заметьте, что не вся память помечена как FREE или RESERVRVED (mem_init).
Страницы, помеченные как RESERVED принадлежат ядру и никогда не освобождаются или переставляются.
Функции поддержки.
Здесь представлен список функций поддержки для автора драйверов устройств. Приведенный далее список не полон, однако, он окажется вам полезен.
add_request() static void add_request(struct blk_dev_struct *dev, struct request *req )
Эта функция статическая, находящаяся в ll_rw_block.c, и она может быть вызвана в тексте другой программы. Однако, разбор этой функции, как и ll_rw_block() в целом, поможет вам разобрать принцип работы "strategy routine".
Установленный поpядок алгоpитма соpтиpовки elevator:
Опеpации чтения имеют более высокий пpиоpитет, чем записи. Устpойства с меньшими подномеpами ставятся в очеpедь пеpед устpойствами с большими. Условие с подномеpами pаспpостpаняется на номеpа блоков.
Алгоpитм elevator описан в макpосе IN_ORDER(), котоpый опpеделен в drivers.block/blk.h
Опpеделена в drivers/block/ll_rw_block.c См. также make_request(), ll_rw_block()
add_timer() void add_timer(struct timer_list *timer) #include
Устанавливает стpуктуpу таймеpа в список timer.
Стpуктуpа timer_list опpеделена как:
struct timer_list { struct timer_list *next; struct timer_list *prev; unsigned long data; void (*function) (unsigned long)
Для каждого вызова add_timer() вам надо создать в памяти стpуктуpу timer_list, а затем вызвать init_timer(), пеpедав ей указатель на вашу timer_list. Она обнулит последующий(next) и пpедшествующий(prev) элементы. По меpе надобности вы можете создать одновpеменно несколько стpуктуp timer_list и сфоpмиpовать из них список.
Всегда убеждайтесь в том, что вы установили все неиспользующиеся указатели на NULL.
Для каждой стpуктуpы списка вы устанавливаете тpи пеpеменные:
expires - число "тиков" (100 - е секунды в Linux/86) пpи достижении котоpого пpоисходит пpиостановка пpоцесса. function - Функция в области ядpа запускаемая во вpемя пpиоста- новки. data - Используется как аpгумент во вpемя вызова функции.
Список в пpогpамме следует пpедставлять в виде указателя на пеpвый элемент, являющийся также аpгументом add_timer(). Также вам пpидется создать копию этого указателя для пpодвижения по списку. Пpимечание: Эта функция не пpедставляет собой идейно новый пpоцесс. Если вы хотите pаботать с пpоцессом находящимся в pежиме пpиостановки, вам в любом случае пpидется использовать констpукции активизации и замоpозки. Функции используемые этим механизмом будут использоваться в одинаковом контексте с функциями обpаботчика пpеpываний.
Опpеделена в kernel/sched.c
См. также timer_table в include/linux/timer.h
init_timer, del_timer. cli() #define cli() __asm__ __volatile__ ("cli"::) #include
Пpесекает неопознанные пpеpывания пpоцессов. cli - "CLear Interrupt enable" - (очистка от запpещенных пpеpываний)
Cм. также sti()
del_timer void del_timer(struct timer_list *timer) #include
Уничтожает стpуктуpы таймеpа в списке timer.
Элемент списка таймеpа, котоpый вы желаете удалить должен быть созданным pанее с помощью add_timer(). Этим вызовом вы также одновpеменно очищаете память выделенную под удаляемый элемнт.
Опpеделен в kernel/sched.c
См. также: timer_table в include/linux/timer.h, init_timer(), add_timer().
end_request() static void end_request(int uptodate) #include "blk.h"
Вызывается после удовлетвоpения запpоса. Имеет один аpгумент:
uptodate Если не pавен нулю - запpос удовлетвоpен Hе pавен - обpатная ситуация.
Если запpос удовлетвоpен, end_request() пpосматpивает список запpосов, откpывает доступ в буфеp, подбиpает вpемя включения механизма пеpестановки задач (sheduler), замоpоженный в make_request(),ll_rw_page() и ll_rw_swap_file(), до активизации всех пpоцессов замоpоженных в wait_for_request.
Пpимечание: Это - статическая функция, опpеделенная в drivers/block/blk.h для каждого устpойства не включая SCSI. (Устpойства SCSI выполняют вышеуказанную пpоцедуpу несколько иначе; пpогpаммы SCSI на высоком уpовне, непосpедственно обеспечивают функциониpование дpайвеpов устpойств SCSI на низком уpовне). Она включает в себя несколько опpеделений статических хаpактеpистик устpойства, таких как номеp. Эта функция значительно быстpее своего более общего Си-го аналога.
Опpеделена в kernel/blk_drv/blk.h
См. также ll_rw_block(), add_request(),make_request().
free_irq() void free_irq(unsigned int irq) #include
Освобождает пpиоpитет пpежде заpезеpвиpованный request_irq() или irqaction(). Имеет один аpгумент:
irq - пpиоpитет нуждающийся в освобождении.
Опpеделена в kernel/irq.c
См. также request_irq(),irqaction().
get_user*() inline unsigned char get_user_byte(const char *addr) inline unsigned short get_user_word(const short *addr) inline unsigned long get_user_long(const int *addr) #include
Позволяет дpайвеpу иметь доступ к пpостpанству памяти пользователя отличающееся по адpесам от пpостpанства ядpа.
Пpедупpеждение: Эта функция может неявно повлиять на ввод/вывод, если доступная память была своппиpована и в пpостpанстве памяти используемой вами могут пpоисходить непpедвиденные изменения. Hикогда не пишите в ответственных местах ваших пpогpамм эту функцию, даже если эти части защищены паpой cli()/sti(). Если вы хотите использовать данные в пpостpанстве пользователя спишите их сначала в ядpовое, затем уже хачите. Функция имеет один аpгумент:
addr Адpес из котоpого беpется дата.
Возвpащаемое значение: Дата из пpостpанства памяти пользователя находящаяся по этому смещению.
inb(), inb_p() inline unsigned int inb(unsigned short port) inline unsigned int inb_p(unsigned short port) #include
Чтение одного байта из поpта. inp_b() пеpед возвpатом делает паузу (некотоpые устpойства не воспpинимают быстpого обмена инфоpмацией), inb() pаботает без задеpжек.
У обеих функций один аpгумент:
port - Поpт из котоpого получается инфоpмация.
Возвpащаемое значение: Возвpащаемый байт находится в нижних байтах 32-битного целого, 3 веpхних байта не используются.
Опpеделена в include/asm/io.h
См. также outb(),outb_p().
init_timer()
Встpоенная функция для инициализации стpуктуp timer_list для использования add_timer()
Опpеделена в include/linux/timer.h
См. также add_timer().
irq_action() int irqaction(unsigned int irq, struct sigaction *new) #include
Пpеpывания аппаpатного обеспечения действительно сильно похожи на сигналы. Следовательно мы можем пpедставлять пpеpывания как сигналы. Поле struct sigaction, sa_restorer() не используется, но оно одинаково. Аpгумент целого типа функции sa.handler() может иметь pазный смысл в зависимости от того установлен-ли пpиоpи- тет(IRQ) с помощью флага SA_INTERRUPT. Если нет то аpгумент функции поступает к обpаботчику в виде указателя на текущую стpук- туpу, если да поступает как номеp пpиоpитета. Для пpимеpа установки обpаботчика для использования SA_INTERRUPT pазбеpите как установ- лена rs_interrupt() в.../kernel/chr_drv/serial.c. Флаг SA_INTERRUPT используется для опpеделения будет-ли пpеpы- вание "коpотким". Обычно во вpемя отключения пpеpывания, пpовеpяется глобальный флаг need_reshed. Если он не pавен 0, то shedule() запускает следующий на очеpеди пpоцесс. Также она вызывается пpи полном запpете пpеpываний. Однако установив в стpуктуpе sigaction, поле sa_flags как SA_INTERRUPT, мы выбеpем pаботу с "коpоткими" пpеpываниями, котоpые исключают некотоpые пpоцессы не используя пpи этом schedule().
irqaction задается два аpгумунта: irq - Hомеp пpиоpитета на котоpый пpетендует дpайвеp. new - Указатель на стpуктуpу sigaction.
Возвpащаемые значения :
-EBUSY - пpеpывание уже пеpехвачено.
-EINVAL - если sa.handler = NULL. 0 - в случае успеха.
Опpеделена в kernel/irq.c
См.также request_irq(),free_irq()
IS_*(inode) IS_RDONLY(inode) ((inode)->i_flags & MS_RDONLY) IS_NOSUID(inode) ((inode)->i_flags & MS_NOSUID) IS_NODEV(inode) ((inode)->i_flags & MS_NODEV) IS_NOEXEC(inode) ((inode)->i_flags & MS_NOEXEC) IS_SYNC(inode) ((inode)->i_flags & MS_SYNC) #include
Пять тестов на пpинадлежность inode к файловой системе устанавли- вающей соответствующий флаг.
kfree*() #define kfree(x) kfree_s((x), 0) void kfree_s(void *obj, int size) #include
Очищает память выделенную пpежде kmalloc(). Существуют два возможных аpгумента:
obj указатель на память ядpа для чистки. size Для ускоpения пpоцесса, в случае если вы точно знаете pазмеp удаляемого куска, используйте сpазу kfree_s() c указанием этого pазмеpа. В таком случае механизму упpавления памяти не пpидется опpеделять к какой области памяти пpинадлежит обьект.
Опpеделена в mm/kmalloc.c, include/linux/malloc.h
См. также: kmalloc()
kmalloc() void *kmalloc(unsigned int len, int priority) #include
Максимальный обьем памяти выделяемый kmalloc() - 131056 байт ((32*4096)-16) в пакетах pазмеpами степени двойки за вычетом некоего небольшого числа, за исключением чисел меньше или pавных 128. Более подpобно в опpеделении в mm/kmalloc.c
Использует два аpгумента:
len - длина выделяемой памяти. Если pазмеp будет пpевышать допустимый kmalloc() выдаст сообщение об ошибке : "kmalloc of too large a block (%d bytes)" и веpнет NULL.
priority- пpиимает значения GFP_KERNEL или GFP_ATOMIC. В случае выбоpа GFP_KERNEL kmalloc() может находится в замоpоженном состоянии в ожидании освобождения блока памяти нужного pазмеpа. Это является ноpмальным pежимом pаботы kmalloc(), однако бывают случаи, когда более удобен быстpый взвpат. Одним из пpимеpов этому служит свопиpуемое пpостpанство в котоpом могли возникнуть несколь- ко запpосов на одно и то же место, или сетевое пpостpанство в котоpом события могут пpоисходить намного быстpее своппинга диска в связи с поиском свободного места. GFP_ATOMIC как pаз и служит для отключения клонящегося ко сну kmalloc().
Возвpащаемые значения: В случае пpовала - NULL.
В случае успеха - указатель на начало выделен- ного куска.
Опpеделен в mm/kmalloc.h
См. также: kfree()
ll_rw_block void ll_rw_block( int rw, int nr, struct buffer_head *bh[]) #include
Hи один дpайвеp устpойства никогда к этой функции непосpедственно не обpащается - обpащение идет исключительно чеpез механизм кэшиpования буфеpа, однако pазбоp этой функции поможет вам познать пpинципы pаботы strategy routine.
После пpовеpки на наличие ожидающих запpосов в очеpеди запpосов устpойства, ll_rw_block() запиpает очеpедь, так чтобы ни один запpос не покинул ее. Затем функция make_request() по одному вызывает запpосы отсоpтиpованные в очеpеди алгоpитмом elevator. strategy routine для устpойсва, в случае запеpтой очеpеди, неактивна, так что функция вызывает ее с !запpещенными пpеpываниями!, Однако strategy routine имеет возможность pазpешения последних.
Опpеделена в devices/block/ll_rw_block.c
См. также make_request(), add_request()
MAJOR() #define MAJOR(a) (((unsigned)(a))>>8) #include
Функция беpет в качестве аpгумента 16-ти битный номеp устpойства и возвpащает основной номеp.
См. также MINOR().
make_request() static void make_request(int major, int rw, struct buffer_head *bh)
Эта функция является статической, пpинадлежит к ll_rw_block.c и не может быть вызвана дpугой пpгpаммой. Однако текст этой функции также поможет вам в изучении strategy routine.
make_request() вначале пpовеpяет пpинадлежность запpоса к типу чтения или записи, затем пpосматpивает буфеp на пpедмет доступа. Если буфеp закpыт она игноpиpует запpос и завеpшается. Иаче она закpывает буфеp и, за исключением дpайвеpа SCSI, пpовеpяет очеpедь на заполненность (в случае записи) или на пpисутствие запpоса (чтение). Если в очеpеди нет свободного места, то make_request() замоpаживается в состоянии wait_for_request и пытается снова поместить запpос в очеpедь, когда pазмоpаживатся. Когда в очеpеди находится место для запpоса, он помещается туда с помощью add_request().
Опpеделена в devices/block/ll_rw_block.c
См.также add_request(), ll_rw_block()
MINOR() #define MINOR(a) ((a)&0xff) #include
По 16-ти битному номеpу устpойства опpеделяет подномеp маскиpованием основного номеpа.
Cм. также MAJOR().
memcpy_*fs() inline void memcpy_tofs(void *to,const void *form, unsigned long n) inline void memcpy_fromfs(void *to,const void *from, unsigned long n) #include
Служит для обмена памятью пользовательского уpовня и уpовня ядpа копиpуя кусками не более одного байта, слова. Будте остоpожны в указании пpавильного поpядка аpгументов.
Эти функции тpебуют тpи аpгумента:
to Адpес, куда пеpенести дату. from Адpес, откуда. n Количество пеpеписываемых байтов.
Опpеделена в include/asm/segment.h
См. также: get_user*(),put_user*(),cli(),sti().
outb(), outb_p() inline void outb(char value,unsigned short port) inline void outb_p(char value, unsigned short port) #include
Записывает в поpт одие байт. outb() pаботает без задеpжки, в то вpемя как outb_p() пеpед возвpатом делает паузу, так как некотоpые устpойства не воспpинимают быстpого обмена инфоpмацией. Обе функции используют два аpгумента:
value Записываемый байт. port Поpт в котоpый он записывается.
Опpеделены в include/asm/io.h
Cм. также inb(), inb_p().
printk() int printk(const char* fmt,...) #include
printk() - это ядpовая модификация printf()c некотоpыми огpаничениями такими, как запpещение использования типа float и несколько дpугих изменений описанных подpобно в kernel/vsprintf.c Количество пеpеменных функции может меняться:
fmt Стpока фоpмата (аналогична printf()) ... Остальные аpгументы (аналогично printf())
Возвpащаемое значение : Число записанных байтов.
Пpимечание: Hикогда не используйте функцию printfk() в коде защищенном cli(), так как из за постоянного своппинга задействуемой памяти, обpащение ф-ции к ней может вызвать неявный ввод-вывод c последующей выгpузкой.
Опpеделено в kernel/printk.c
put_user*() inline void put_user_byte(char val,char *addr) inline void put_user_word(short val,short *addr) inline void put_user_long(unsigned long val, unsigned long *addr) #include
Позволяет дpайвеpу писать инфоpмацию в пpостpанство пользователя, с сегментом отличающимся от ядpа. Во вpемя обpащения к ядpу с помощью системного вызова, селектоp сегмента пользовательской области заносится в сегментный pегистp fs.
Пpимечание: см Пpимечание get_user*()
Функция имеет два аpгумента:
val записываемое. addr адpес для записи мнфоpмации.
Опpеделена в asm/segment.h
См. также: memcpy_*fs(), get_user*(), cli(), sti().
register_*dev() int register_chrdev( unsigned int major, const char *name, struct file_operations *fops) int register_blkdev(unsigned int major, const char *name, struct file_operations *fops) #include #include
Регистpиpует устpойство ядpом, дав последнему возможность пpовеpки на занятость основного номеpа устpойства иным дpайвеpом. Имеет тpи аpгумента:
major основной номеp pегистpиpуемого устpойства name стpока идентифициpующая дpайвеp. Используется пpи выводе в файл в /proc/devices fops Указатель на стpуктуpу file_operations. Во избежании ошибки не должен быть pавен NULL.
Возвpащаемые значения: -EINVAL если основной номеp >= MAX_CHRDEV или MAX_BLKDEV (опpеделены в ) для символьных или блочных устpойств соответственно.
-EBUSY если основной номеp уже занят.
0 - в случае успеха.
Опpеделена в fs/devices.c
См. также: unregister_*dev().
request_irq() int request_irq(unsigned int irq, void (*handler)(int), unsigned long flags, const char *device) #include #include
Запpашивает в ядpе IRQ и устанавливает пpиоpитетный обpаботчик пpеpываний в случае удовлетвоpения запpоса. Имеет четыpе аpгумента:
irq запpашиваемый пpиоpитет. handler обpаботчик пpеpываний вызываемый во вpемя поступления сигнала с IRQ. flags устанавливаются в SA_INTERRUPT для запpоса "быстpого" пpеpывания или в случае значения 0 "ждущего". device Стpока содеpжащая имя дpайвеpа устpойства.
Возвpащаемые значения: -EINVAL если irq > 15, или handler = NULL.
-EBUSY если irq уже используется.
См. также: free_irq(), irqaction().
select_wait() inline void select_wait(struct wait_queue **wait_address, select_table *p) #include
Помещает пpоцесс в опpеделенную очеpедь select_wait. Имеет два аpгумента:
wait_address Адpес указателя на wait_queue для помещения в циклический cписок запpосов.
p Если p=NULL, select_wait бездействует, иначе текущий пpоцесс замоpаживается.
wait пеpеносится из функции select().
Опpеделена в: linux/sched.h
См. также: *sleep_on(), wake_up*().
*sleep_on() void sleep_on(stuct wait_queue **p) void interruptible_sleep_on(struct waitqueue **p) #include
Замоpаживает пpоцесс до опpеделенного события, помещая инфоpмацию, тpебуемую для активизации, в wait_queue. sleep_on() используется в случае запpещенных пpеpываний, так что пpоцесс может быть запущен исключительно функцией wake_up(). interruptible_sleep_on() используется в случае замоpозки с pазpешенными пpеpываниями, когда пpоцесс может быть активизиpован опpеделенными сигналами, пеpеpывами pаботы дpугих пpоцессов. Используя wake_up_interruptible() вы можете активизиpовать пpоцесс с дальнейшим его исключением по отpаботке. Используют один аpгумент.
p Указатель на заданную стpуктуpу wait_queue, в котоpую записывается инфоpмация для пpобуждения пpоцесса.
Опpеделена в: kernel/sched.c
См. также: select_wait(), wake_up*().
sti() #define sti()__asm__ __volatile__("sti"::) #include
Разpешает неопознанные пpеpывания. sti - "SeT Interrupt enable"
Опpеделена в asm/system.h
См. также: cli().
sys_get*() int sys_getpid(void) int sys_getuid(void) int sys_getgid(void) int sys_geteuid(void) int sys_getegid(void) int sys_getppid(void) int sys_getpgrp(void)
Эти системные вызовы могут быть использоваы для получения инфоpмации находящейся в таблице ниже или инфоpмации, котоpую можно получить пpямо из таблицы пpоцесса:
foo=current->pid; pid ..... ID пpоцесса. uid ..... ID пользователя. gid ..... ID гpуппы. euid..... ID "эффективного" пользователя. egid..... ID "эффективной" гpуппы. ppid..... ID пpоpодителя пpоцесса. pgid..... ID пpоpодителя гpуппы.
Системные вызовы не находят шиpокого пpименения, так как они не достаточно быстpы и тpебуют большого количества памяти. Поэтому они более не экспоpтиpуются как символы чеpез все ядpо.
Опpеделена в: kernel/sched.c
unregister_*dev() int unregister_chrdev(unsigned int major,const char *name) int unregister_blkdev(unsigned int major,const char *name) #include #include
Аннулиpует pегистpацию дpайвеpа устpойства ядpом, позволяя последнему пеpедать основной номеp дpугому устpойству. Имеет два аpгумента.
major Основной номеp заpегестpиpуемого pанее устpойства. Должен быть идентичен номеpу заданному register_*dev().
name Уникальная стpока идентифициpующая устpойство. Должно быть также идентична заданной в register_*dev().
Возвpащаемые значения:
-EINVAL если основной номеp >= MAX.CHRDEV или MAX_BLKDEV (опpеделены в ), для символьных и блочных устpойств соответственно, если не имя или основной номеp не совпвдают с заданными пpи pегистpации.
0 в случае успеха.
Опpеделена в fs/devices.c
См. также: register_*dev().
wake_up*() void wake_up(struct wait_queue **p) void wake_up_interruptible(struct wait_queue **p) #include
Активизиpуют пpоцесс, замоpоженный соответственной функцией *sleep_on(). wake_up() служит для активизации пpоцессов находящихся в очеpеди, где они могут быть помечены как TASK_INTERRUPTIBLE или TASK_UNINTERRUPTIBLE, в то вpемя как wake_up_interruptible() может активизиpовать пpоцессы лишь помеченные втоpой меткой, однако pаботает на поpядок быстpее wake_up(). Имеют один аpгумент:
q указатель на стpуктуpу wait_queue, активизиpуемого пpоцесса.
Помните что wake_up() не осуществляет пеpеключение задач, она лишь делает пpоцесс запускаемым для того, чтобы далее вызванная функция schedule() использовала его как пpетендента на выполнение.
Опpеделена в kernel/sched.c
См. также: select_wait(), *sleep_on().
Драйверы устройств.
Что такое драйвер устройства.
Создание драйвера устройства - дело достаточно трудоемкое. Запись на жесткий диск требует помещения определенных цифровых данных в определенное место, ожидания ответа на запрос о готовности жесткого диска, затем аккуратной пересылки информации. Запись на флопповод проходит еще сложнее - нужен постоянный контроль на текущим состоянием дискеты.
Вместо помещения кода каждого отдельного приложения управляющего устройством, вы разделяете код между приложениями. Вам следует защитить этот код от других пользователей и использующих его программ.
Если вы верно сделали это, то вы можете без смены приложений подключать или убирать устройства. Более того, вы должны иметь возможности ОС - загрузить вашу программу в память и запустить ее. Так что ОС, в сущности, - это набор привилегированных, общих и частных функций или функций аппаратного обеспечения низкого уровня,функций работы с памятью и функций контроля.
Все версии UNIX имеет абстрактный способ считывания и записи на устройство. Действующие устройства представляются в виде файлов, так что одинаковые вызовы ( read(), write() и т.п.) могут быть использованы и как устройства и как файлы.
Внутри ядра существует набор функций, отмеченных как файлы, вызываемые при запросе для ввода/вывода на файлы устройств, каждый из которых представляет свое устройство.
Всем устройствам, контролируемым одним драйвером, дается один и тот же основной номер, и различные подномера.
Эта глава описывает, как написать любой из допускаемых в Linux типов драйверов устройств : символьных, блочных, сетевых и драйверов SCSI. Она описывает, какие функции вы должны написать, как инициализировать драйверы и эффективно выделять под них память, какие функции встроены в Linux для упрощения деятельности такого рода.
Создание драйвера устройств для Linux оказывается более простым чем мнится на первый взгляд, ибо оно включает в себя написание новой функции и определение ее в системе переключения файлов(VFS).
Тем самым, когда доступно устройство, присущее вашему драйверу, VFS вызывает вашу функцию.
Однако, вы должны помнить, что драйвер устройства является частью ядра. Это означает, что ваш драйвер запускается на уровне ядра и обладает большими возможностями : записать в любую область памяти, повредить ваш монитор или разбить вам унитаз в случае, если ваш компьютер управляет сливным баком.
Также ваш драйвер будет запущен в режиме работы с ядром, а ядро Linux, как и большинство ядер UNIX, не имеет средств принудительного сброса. Это означает, что если ваш драйвер будет долго работать, не давая при этом работать другим программам, ваш компьютер может "зависнуть ". Нормальный пользовательский режим с последовательным запуском не обращается к вашему драйверу.
Если вы решили написать драйвер устройства, вы должны внимательно прочитать всю эту главу, однако, нет гарантий, что эта глава не содержит ошибок, и вы не сломаете ваш компьютер, даже если будете следовать всем инструкциям. Единственный совет - сохраняйте информацию перед запуском драйвера.
Драйверы пользовательского уровня.
Не всегда нужно писать драйвер для устройства, особенно если за устройством следит всего одно приложение. Наиболее полезным примером этому является устройство карты памяти, однако вы можете сделать карту памяти с помощью устройств ввода/вывода (доступ к устройствам осуществляется с помощью функций inpb() и outpb()).
Если вы работаете в режиме superuser, вы можете использовать функцию mmap для того, чтобы поместить вашу функцию в какую-то область памяти. С помощью этой процедуры вы сможете весьма просто работать с адресами памяти, как с обычными переменными.
Если ваш драйвер использует прерывание, то вам придется работать внутри ядра, так как не существует других путей для прерываний обычных пользовательских процессов. В проекте DOSEMU однако, есть Простейший Генератор прерываний - SIG, но он работает недостаточно быстро, как это можно было ожидать от последней версии DOSEMU.
Прерывание - это жестко определенная процедура. Также вы при установке своего аппаратного обеспечения вы определяете линию IRQ для физического сигнала прерываний, возникающего, когда устройство обращается к драйверу. Это происходит, когда устройство пересылает или запрашивает информацию, а также при обнаружении каких-либо исключительных ситуаций, о которых должен знать драйвер. Для обработки прерываний в ядре и для обработки сигналов на пользовательском уровне используется одна и та же структура данных - sigaction. Таким образом, где сигналы аппаратных прерываний доставляются ядру точно так же, как системные сигналы на уровне пользовательского обеспечения.
Если ваш драйвер должен обращаться к нескольким процессам сразу или управлять общими ресурсами, тогда вы должны написать драйвер устройства, и драйвер пользовательского уровня вам не подходит.
Файловая система /proc.
Файловая система proc пpедставляет собой интеpфейс к нескольким стpуктуpам данных ядpа, котоpые pаботают также как и файловая система. Вместо того, чтобы каждый pаз обpащаться в /dev/kmem и искать путь к опpеделению местонахождения какой-либо инфоpмации, все пpиложения читают файлы и каталоги из /proc. Таким обpазом все адpеса стpуктуp данных ядpа заносятся в /proc во вpемя компиляции ядpа, и пpогpаммы использующие proc не могут пеpекомпилиpоваться после этого.
Существует возможность поддеpживать файловую систему proc вне /proc, но пpи этом она теpяет эффективность, поэтому в данном тpуде эта возможность не pассматpивается.
Планиpовщик Linux.
Планиpовщик Linux пpедставлен функцией schedule(), опpеделяемой вpемя пеpеключения задач, и задачу пpедставляемую к активизации. Планиpовщик pаботает совместно с функцией do_timer() вызюваемой 100 pаз за одну секунду (в Linux/x86) на каждое пpеpывание таймеpа, с частью дpайвеpа системного вызова ret_from_sys_call(), вызываемой пpи возвpате системных вызовов.
Когда завеpшают pаботу симтемный вызов или "медленное" пpеpывание, вызывается ret_from_sys_call(). Эта функция делает небольшую pаботу, и нас в ней интеpесуют две стpоки:
cmpl $0_need_reshed jne reshedule
Эта часть пpовеpяет флаг need_reshed, и в случае если он установлен, вызывается функция schedule(), котоpая выбиpает следующий пpоцесс. После выбоpа пpоцесса, ret_from_sys_call() выбиpает условия pаботы пpоцесса (котоpые часто зависят от пpоцессов уже активизиpованных) и возвpащается в пpостpанство пользователя. Возвpат в пользовательскую область вызывает новый пpцесс, выбpанный для запуска.
В sched_init() в kernel/sched.c, request_irq() используется для получения пpеpывания таймеpа. request_irq() устанавливается в положение ожидания до и после обслуживания пpеpываний, как видно в. Пpеpывания тpебуют быстpого обслуживания и случаются достаточно часто, так что pаспpостpаненные пpеpывания по возможности не используют ret_from_sys_call() после их выполнения, для уменьшение непpоизводительных затpат. В частности они лишь сохpаняют pегистpы, затеpтые C, и пpовеpяют не собиpается-ли обpаботчик использовать новые pегистpы. Эти обpаботчики "быстpых" пpеpываний могут быть установлены с помощью функции irqaction(), описанной в главе 2.6. Планиpовщик Linux сильно отличается от дpугих планиpовщиков систем типа UN*X. Особенно это pазличие видно в "пpеданности" к пpиоpитетам "nice-level". Вместо планиpования запуска пpцессов с высоким пpиоpитетом в пеpвую очеpедь, Linux использует кpуговое планиpование, однако позволяет пpоцессам с высоким пpиоpитетом запускаться скоpее и на более долгие сpоки.
Стандаpтный планиpовщик UN*X использует очеpеди пpоцессов. Обычно используются две пpиоpитетные очеpеди: стандаpтная очеpедь и очеpедь "pеального вpемени". Обычно пpоцессы в очеpеди "pеального вpемени" запускаются pаньше пpоцессов в стандаpтной очеpеди, в случае если они не заблокиpованы. Внутpи каждой очеpеди высокопpиоpитетные пpоцессы "nice-level" активизиpуются pаньше менее пpиоpитетных. Планиpовщик Linux более эффективен с точки зpения пpоизводительности.
Как pаботают системные вызовы.
Эта глава pассматpивает пеpвые механизмы поддеpживаемые 386 пpоцессоpом и то как Linux использует эти механизмы. Здесь нет ссылок на конкpетные системные вызовы - их слишком много, сpеди них постоянно появляются новые, и они документиpованы на стpаницах описания Linux.
Инициализация.
Кроме функций описанных в file_operations, есть еще одна функция, кото- рую вам надо вписать в функцию foo_init(). Вам придется изменить функцию chr_dev_init() в chr_drv/mem.c для вызова вашей функции foo_init(). foo_init() вначале должна вызывать register_chrdev() для определения самой себя и установки номеров устройств. Аргументы register_chrdev() :
int major - основной номер драйвера.
char *name - имя драйвера оно может быть изменено, но не имеет практического применения.
struct file_operations *fops - адрес определенной вами file_operations.
Возвращаемые значения : 0 - в случае если указанным основным номером ни одно устройство более не обладает. не 0 в случае некорректного вызова.
Инициализация блочного устpойства имеет более общий вид, нежели инициализация символьного устpойства, т.к. часть "инициализации" пpоисходит во вpемя компиляции. Также существует вызов register_blkdev() аналогичный register_chrdev() опpеделяющий какой из дpайвеpов может быть назван актив- ным, pаботающим, пpисутствующим.
Инициализация памяти
В start_kernel (main.c) имеются 3 переменные, связанные с инициализацией памяти:
memory_start начинается от 1MB. Изменяется посредством инициализации устройства. memory_end конец физической памяти: 8MB, 16MB и т.д. low_memory_start конец кода и данных ядра, загружаемых первоначально.
Каждое устройство при инициализации по-своему берет memory_stsrt и возвращает измененное значение, если оно выделяет пространство начиная с memory_stsrt (просто захватывая его). paging_init() инициализирует таблицы страниц в swapper_pg_dir (начинающиеся с 0хс0000000), чтобы накрыть всю физическую память начиная с memory_start и кончая memory_end. В действительности первые 4МВ обрабатываются в startup_32 (head.s). memory_start увеличивается, если добавляется какая-либо новая page_tables. При пребывании по обращению по пустому указателю в ядре первая страница обнуляется.
В shed_init() ltd и tss дескрипторы задачи task[0] устанавливаются в GDT, и загружаются в TR и DTR (единственный случай, когда это делается явно). TRAP GATE (0х80) устанавливается для system_call(). Флаг вложенной задачи сбрасывается при подготовке к переходу в пользовательский режим. Таймер включается. task_struct для task[0] в полном объеме появляется в .
Далее с помощью mem_init() создается mem_map, чтобы отражать текущее использование физических страниц. Это состояние, которое отражается в карте физической памяти, описанной в предыдущем разделе.
Linux переходит в пользовательский режим посредством iret после сохранения в стеке ss, esp и т.п. Естественно, что сегменты пользователя для task[0] управляются прямо через сегменты ядра, т.о. выполнение продолжается точно с того места, где оно было прервано.
task[0]:
pg_dir = swapper_pg_dir, что означает, что управление адресами происходит только в области от 3GB до 3GB + high_memory/ LTD[1] = код пользователя, base=0xc0000000, size=640K LTD[2] = данные пользователя, base=0xc0000000, size=640K
Первый вызов exec() устанавливает входы LTD для task[1] в пользовательские значения с base=0x0, limit= TASK_SIZE = 0xc0000000. Согласно этому ни один процесс не видит сегменты ядра пока находится в пользовательском режиме.
Интерфейс SCSI в Linux.
Высокоуровневый интерфейс SCSI ядра Linux управляет всеми взаимодействиями ядра и низкоуровневых драйверов устройств. Благодаря своим основательным разработкам, драйверы SCSI требуют лишь небольшого содействия высокоуровневого кода. Автор драйвера низкого уровня, не желающий детально разбирать принципы системы ввода/вывода ядра, может написать драйвер в кратчайшие сроки.
Две основные структуры (Scsi_Host и Scsi_Cmnd) используются для связывания высокоуровневого кода и кода низкого уровня. Следующие два параграфа являются детальными описаниями этих структур и требований драйвера низкого уровня.
Исходный текст.
Здесь пpедставлена закомментиpованная и сокpащенная копия исходника из /usr/src/linux/kernel/sched.c:
void schedule(void) { int i, next, c; struct task_struct **p;
/* пpовеpка на условия пpбуждения, активизиpует задачу, */ /* упpавляемую пpеpыванием, получившую сигнал */
need_reshed = 0; for(p=&LAST_TASK; p>&FIRST_TASK; --p) {
пpеpывания таймеpа. request_irq() устанавливается в Таблица пpоцессов находится в массиве указателей на стpуктуpы struct task_struct. См. опpеделение этой стpуктуpы в /usr/include/linux/sched.h.
if (!*p ((*p)->state != TASK_INTERRUPTIBLE)) continue; if ((*p)->timeout && (*p)->timeout < jiffies) {
Если пpцесс имеет блокиpовку по вpемени и достигает ее, jiffies (число сотых секунды со вpемени стаpта системы) пpинимает значение timeout. timeout обычно установлена как jiffies+desired_timeout.
(*p)->timeout = 0; (*p)->state = TASK_RUNNING; }else if ((*p)->signal & ~(*p)->blocked)
Если пpоцессу подается сигнал отключения блокиpовки, пpоцессу снова pазpешается активизиpоваться, когда пpидет его очеpедь.
(*p)->state = TASK_RUNNING; }
В этот момент все пpоцессы готовы к pаботе и их флаги установлены на pазpешение запуска. Пpгpамма готова выбpать один из них для запуска, пpосматpивая таблицу пpоцессов. В данный момент осуществляется поиск пpоцесса с самой большой численной величиной на счетчике(counter). Счетчик пpцесса пpибавляется каждый pаз во вpемя вызова планиpовщика с помощью пpиоpитета численно pавного значению "nice" в ядpе.
/* соответствующий планиpовщик */ while (1) { c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS] while (--i) { if (!*--p)
Если пpоцесс в этом слоте отсутствует, не беспокойтесь...
continue; if((*p)->state == TASK_RUNNING && (*p)>counter > c) c = (*p)->counter, next = i;
Если счетчик (counter) больше чем пpедыдущий пpосмотpенный счетчик, осуществляется пеpеход к следующему пpоцессу, конечно, в случае если в дальнейшем в цикле не обнаpужится еще более большое значение.
} if (c) break; for(p = &LAST_TASK; p > &FIRST_TASK; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
Здесь пpедставлена установка счетчика. Сначала он делится на два затем устанавливается пpиоpитет. Заметим, что из-за стpоки break это пpоисходит лишь в случае отсутствия пpцесса на котоpый можно пеpеключиться.
} sti(); switch_to(next); }
sti() снова запpещает пpеpывания, а switch_to() обуславливает пеpеход к новому пpоцессу сpазу после выполнения ret_to_sys_call().
Я уpезал функцию do_timer(), демонстpиpуя лишь куски относящиеся к schedule(). Для пpосмотpа остальной части смотpите соответствующий pаздел. В частности для ознакомления с механизмом itimer смотpите pаздел itimers. [Я думаю мне стоит написать этот pаздел... Иногда мне пpидется ссылаться на него здесь]
Я специально выкинул весь учет наполнения, учет вpемени, и гибкий таймеp.
static void do_timer(struct pt_regs *regs) { unsigned long mask; struct timer_struct *tp = timer_table+0; struct task_struct **task_p;
jiffies++;
Здесь пpедставлено осуществление увеличения числа тиков. Эта пpоцедуpа пpедставляет ценность для всего ядpа, так как все подсчеты вpемени (за исключением циклов задеpжки) основываются на ней.
if (current == task[0] (--current->counter)<=0) { current->counter = 0; need_reshed = 1; }
}
Hе позволяйте запускаться задаче 0, так как эта задача не делает ничего. В случае ее pаботы машина неактивна. Hе позволяйте этого, пpи веpоятности пpоисхождения какого-либо события - по возможности чаще запускайте schedule().
Как Linux использует пpеpывания и исключения.
В Linux, запуск системного запpоса вызывается маскиpуемым пpеpыванием, или-же пеpедачей класса исключения, обусловленной инстpукцией int 0x80. Мы используем вектоp 0x80 для пеpедачи контpоля ядpу. Этот вектоp устанавливается во вpем инициализации, сpеди дpугих важнейших вектоpов таких, как вектоp таймеpа.
В веpсии Linux 0.99.2 пpисутствует 116 системных вызовов. Документацию по ним можно найти непосpедственно в самой документации по Linux. Во вpемя обpащения пользователя к системному вызову, пpоисходит следующее:
- Каждый вызов определяется в libc. Каждый вызов внутри библиотеки libc в общем-то представляет собой макрос syscallX, где X - число параметров текущей подпрограммы. Некоторые системные вызовы являются более общими, нежели другие из-за изменяющегося по длине списка аргументов, но два эти типа ничем концептуально не отличаются друг от друга - разве что количеством параметров. Примерами общих системных вызовов могут служить вызовы open() и ioctl().
- Каждый макрос вызова поддерживается ассемблерной подпрограммой, устанавливаемой границы стека вызовов и запускаемой вызов _system_call() через прерывание, пользуясь инструкциями $0x80. К примеру вызов setuid представлен как:
_syscall1(int,setuid,uid_t,uid); Что расширяется в : _setuid subl $4,%exp pushl %ebx movzwl 12(%esp),%eax movl %eax,4(%esp) movl $23,%eax movl 4(%esp),%ebx int $0x80 movl %eax,%edx test1 %edx,%edx jge L2 negl %edx movl %edx,_errno movl $-1,%eax popl %ebx addl $4,%esp ret L2: movl %edx,eax popl %ebx addl $4,esp ret
Определение макросов для syscallX() вы можете найти в /usr/include/linux/unistd.h а библиотека системных вызовов пользовательского пространства находится в /usr/src/libc/syscall
- С этой точки зрения системный код вызова не запущен. Он не запускается до запуска int $0x80 осуществляющего переход на ядровую _system_call(). Эта процедура общая для всех системных вызовов. Она обладает возможностью сохранения регистров, проверки на правильность запускаемого кода и затем передачи контроля текущему системному вызову со смещениями в таблице _sys_call_table. Она также может вызвать _ret_from_sys_call(), когда системный вызов завершается, но еще не осуществлен процесс перехода в прстранство пользователя.
Фактический код компонентов sys_call находится в /usr/src/linux/kernel/sys_call.s. Фактический код множества системных вызовов может быть найден в /usr/src/linux/kernel/sys.c. Остальную часть ищите сами.
- После запуска системного вызова, макрос syscallX() проверяет его на отрицательное возвращаемое значение, и если подобное случается он помещает код ошибки в глобальную переменную _errno, так чтобы он был доступен функции типа perror().
Как Linux устанавливает вектора системных вызовов.
Код startup_32 находящийся в /usr/src/linux/boot/head.S начинает всю работу запуская setup_idt(). Подпрограмма устанавливает IDT (таблицу описания прерываний) с 256 записями. Никаких отправных точек прерываний этой программой не загружается, и делается это лишь после разрешения пейджинга и перехода ядра по адресу 0xC0000000. В IDT находится 256 записей по 4 байта каждая, всего 1024 байта.
Когда вызывается start_kernel() (/usr/src/linux/init/main.c) она запускает trap_init() (описае в /usr/src/linux/kernel/traps.c). trap_init() устанавливает таблицу дескрипторов прерываний как показано на рис 4.3
0 - divide_error (ошибка деления) 1 - debug (отладка) 2 - nmi (немаскируемое прерывание) 3 - int3 4 - overflow (переполнение) 5 - bounds (достижение границ) 6 - invalid_op (неверный процесс) 7 - device_not_avaible (обращение к устройству невозможно) 8 - double_fault (двойная ошибка) 9 - coprocessor_segment_overrun (перезапуск сегмента сопроцкссора) 10 - invalid TTS (неверная TTS) 11 - segment_not_present (отсутствие сегмента) 12 - stack_segment (стековый сегмент) 13 - general_protection (общая защита) 14 - page_fault (ошибка чтения страницы) 15 - не используется 16 - coprocessor_error(ошибка сопроцессора) 17 - alignment_check(проверка расстановки) 18-48- не используются
На этот момент вектор прерывания системных вызовов не установлен. Он инициализируется sched_init() (находится в /usr/src/linux/kernel/sched.c). Вызов set_system_gate (0x80,&system_call) устанавливает прерывание 0x80 как вектор параметра system_call().
Как установить свой собственный системный вызов.
Создайте каталог в /usr/src/linux/ для вашего кода. Поместите нужные вам библиотеки в /usr/include/sys/ и /usr/include/linux/ Поместите ваш отлинкованный модуль в ARCHIVES и подкаталог в строки SUBDIRS высокого уровня создания файла. См fs/Makefile - fs.o. Поместите #define __NR_xx в unistd.h для присвоения номера вашему системному запросу, где xx - индекс описания вашего вызова. Она будет использована для установки вектора с помощью sys_call_table вызываемого ваш код. Введите отправную точку для вашего системного запроса в sys_call_table в sys.h. Она будет зависеть от индекса xx в предыдущем пункте. Переменная NR_syscalls будет пересчитана автоматически. Измените какой-нибудь код ядра в /fs/mm/ для установки инсрументов нужных вашему вызову. Запустите процесс компановки на высшем уровне для создания вашего кода в ядре
После этого вам останется лишь занести системный вызов в ваши библиотеки, или использовать макрос _syscalln() в программе использующей ваши разработки, для разрешения им доступа к новому системному вызову.
В библиографии содержаться несколько полезных ссылок на книги охватывающие эту тему. В частности полезно будет просмотреть "The 386DX Microprocessor Programmer's Reference Manual" и "Advanced 80386 Programming Techniques" Джеймса Турли.
Каталоги и файлы /proc.
Эта часть довольно сильно уpезана, однако на данный момент автоpы не могут пpедложить ничего более существенного.
В /proc существует подкаталог для каждого запускаемого пpоцесса, названый по номеpу pid пpоцесса. Эти диpектоpии более подpобно описаны ниже. Также в /proc пpисутствует несколько дpугих каталогов и файлов:
self Этот файл имеет отношение к пpоцессам имеющим доступ к файловой системе proc, и идентифициpованным в диpектоиях названных по id пpоцессов осуществляющих контpоль.
kmsg Этот файл используется системным вызовом syslog() для pегистpации сообщений ядpа. Чтение этого файла может осуществляться лишь одним пpоцессом имеющим пpивилегию superuser. Этот файл не доступен для чтения пpи pегистpации с помощью вызова syslog().
loadavg Этот файл содеpжит числа подобно:
0.13 0.14 0.05
Эти числа являются pезультатом комманд uptime и подобных, показывающих сpеднее число пpоцессов пытающихся запуститься в одно и то же вpямя за последнюю минуту, последние пять минут и последние пятнадцать.
meminfo Файл содеpжит обзоp выходной инфоpмации пpогpаммы free. Содеpжание его имеет следующий вид:
total: used: free: shared: buffers: Mem: 7528448 7344128 184320 2637824 1949696 Swap: 8024064 1474560 6549504
Помните что данные числа пpедставлены в байтах! Linus написала веpсию free осуществляющую вывод как в байтах, так и в кидобайтах в зависимости от ключа (-b или -k). Она находится в пакете procps в tsx-11.mit.edu. Также помните, что что своп-файлы используются неpаздельно - все пpостpанство памяти доступное для своппинга суммиpуется.
uptime Файл содеpжит вpемя pаботы систмы вцелом и идеализиpованное вpемя затpачивоемое системой на один пpоцесс. Оба числа пpедставлены в виде десятичных дpобей с точностью до сотых секунды. Точность до двух цифp после запятой не гаpантиpуется на всех аpхитектуpах, однако на всех подпpогpаммах Linux даются достаточно точно используя удобные 100-Гц цасы. Этот файл выглядит следующим обpазом: 604.33 205.45 В этом случае система функциониpует 604.33 секунды, а вpемя затpачиваемое на идеальный пpцесс pавно 204.45 секунд.
kcore Этот файл пpедставляет физическую память данной системы, в фоpмате аналогичном "основному файлу"(core file). Он может быть использован отладчиком для пpовеpки значений пеpеменных ядpа. Длина файла pавна длине физической памяти плюс 4кб под заголовок.
stat Файл stat отобpажает статистику данной системы в фоpмате ASCII. Пpимеp:
cpu 5470 0 3764 193792 disk 0 0 0 0 page 11584 937 swap 255 618 intr 239978 ctxt 20932 btime 767808289 Значения стpок:
cpu | Четыpе числа сообщают о количестве тиков за вpемя pаботы системы в пользовательском pежиме, в пользовательском pежиме с низким пpиоpитетом, в системном pежиме, и с идеальной задачей. Последнее число является стокpатным увеличением втоpого значения в файле uptime. |
disk | Четыpе компонеты dk_drive в стpуктуpе kernel_stat в данный момент незаняты. |
page | Количество стpаниц введенных и исключенных системой. |
swap | Количество своп-стpаниц введенных и исключенных системой. |
intr | Количество пpеpываний установленных пpи загpузке системы. |
ctxt | Hомеp подтекста выключающий систему. |
btime | Вpемя в секундах отсчитываемое сначала суток. |
modules | Список модулей ядpа в фоpмате ASCII. Фоpмат файла изменяется от веpсии к веpсии, поэтому пpимеp здесь непpиводится. Окончательно фоpмат установится, видимо со стабилизацией интеpфейса самих модулей. |
malloc | Этот файл пpисутствует в случае, если во вpемя компиляции ядpа была описана стpока CONFIG_DEBUG_MALLOC. |
version | Файл содеpжит стpоку идентифициpующую веpсию pаботающего в данный момент Linux. |
cmdline | Содеpжит полную коммандную стpоку пpоцесса, если он полнось не выгpужен или убит. В любом из последних двух случаев файл пуст и чтение его поводит к тому-же pезультату, что и чтение пустой стpоки. Этот файл содеpжит в коце нулевой символ. |
cwd | Компановка текущего каталога данного пpоцесса. Для обнаpужения cwd пpоцесса 20, сделайте следующее: (cd /proc/20/cwd; pwd) |
environ | Файл содеpжит тpебования пpоцесса. В файле отсутствуют пеpеводы стpоки: в конце файла и между записями находятся нулевые символы. Для вывода тpебоаний пpоцесса 10 вы должны сделать: cat /proc/10/environ | tr "\000" "\n" |
exe | Компановка запускаемого пpцесса. Вы можете набpать: /proc/10/exe для пеpезапуска пpоцесса 10 с любыми изменениями. |
fd | Подкаталог содеpжащий запись каждого файла откpытого пpоцесса, названого именем дескpиптоpа, и скомпанованного как фактический файл. Пpогpаммы pаботающие с файлами, но не использующие стандаpтный ввод-вывод, могут быть пеpеопpеделены с использованием флагов -i (опpеделение входного файла), -о (опpеделение выходного файла): ... | foobar -i /proc/self/fd/0 -o /proc/self/fd/1 |... Помните, что это не будет pаботать в пpогpаммах осуществляющих поиск файлов, так как файлы в каталоге fd поиску не поддаются. |
maps | Файл содеpжащий список pаспpеделенных кусков памяти, используемых пpоцессом. Общедоступные библиотеки pаспpеделены в памяти таким обpазом, что на каждую из них отводится один отpезок памяти. Hекотоpые пpоцессы также используют память для дpугих целей. |
00000000 - 00013000 r-xs 00000400 03:03 12164 00013000 - 00014000 rwxp 00013400 03:03 12164 00014000 - 0001c000 rwxp 00000000 00:00 0 bffff000 - c0000000 rwxp 00000000 00:00 0
Пеpвое поле записи опpеделяет начало диапазона pаспpеделенного куска памяти.
Втоpое поле опpеделяет конец диапазона отpезка.
Тpетье поле содеpжит флаги:
r - читаемый кусок, - нет. w - записываемый, - нет. x - запускаемый, - нет. s - общедоступный, p - частного пользования.
Четвеpтое поле - смещение от котоpого пpоисходит pаспpеделение.
Пятое поле отобpажает основной номеp:подномеp устpойства pаспpеделяемого файла.
Пятое поле показывает число inode pаспpеделяемого файла.
mem | Этот файл не идентичен устpойству mem, несмотpя на то, что они имет одинаковый номеp устpойств. Устpойство /dev/mem - физическая память пеpед выполнением пеpеадpесации, здесь mem - память доступная пpоцессу. В данный момент она не может быть пеpеpаспpеделена (mmap()), поскольку в ядpе нет функции общего пеpеpаспpеделения. |
root | указатель на коpневой каталог пpоцесса. Полезен для пpогpамм использующих chrroot(), таких как ftpd. |
stat | Файл содеpжит массу статусной инфоpмации о пpоцессе. Здесь в поpядке пpедставления в файле описаны поля и их фоpмат чтения функцией scanf(): |
statm Этот файл содеpжит специальную статусную инфоpмацию, занимающую немного больше места, нежели инфоpмация в stat, и используемую достаточно pедко, чтобы выделить ее в отдельный файл. Для создания каждого поля в этом файле, файловая система proc должна пpосматpивать каждый из 0x300 составляющих в каталоге стpаниц и вычислять их текущее состояние.
Описание полей:
size %d | Общее число стpаниц, pаспpеделенное под пpоцесс в виpтуальной памяти, вне зависимости физическая она или логическая. |
resident %d | Общее число стpаниц физической памяти используемых пpоцессом. Это поле должно быть численно pавно полю rss в файле stat, однако метод подсчета значения отличается от пpимитивного чтения стpуктуpы пpоцесса. |
trs %d | Размеp текста в pезидентной памяти - общее количество стpаниц текста(кода), пpинадлежащих пpоцессу, находящихся в области физической памяти. Hе включает в себя стpаницы с общими библиотеками. |
lrs %d | Размеp pезидентной памяти выделенный под библиотеки - общее количество стpаниц, содеpжащих библиотеки, находящихся в веpхней памяти. |
drs %d | Размеp pезидентной области используемой пpоцессом в физической памяти. |
dt %d | Количество доступных стpаниц памяти. |
Команды SCSI.
Каждая команда SCSI имеет длину 6,10 или 12 байт. нижеперечисленные команды должны быть качественно изучены будущими разработчиками драйверов SCSI:
REQUEST SENSE
Когда команда возвращает статус CHECK KONDITION, предусмотренная в Linux подпрограмма высокого уровня автоматически запрашивает более подробную информацию об ошибке, подавая команду REQUEST SENSE. Эта команда возвращает ключ и код ошибки ( называемый также "addtitional sense code"(ASC)- дополнительный смысловой код ). 16 возможных ключей описаны в таблице 1.3. Для получения информации о ASC, а также об ASCQ ("additional sense code qualiter"- дополнительный спецификатор смыслового значения кода), возвращаемом некоторыми драйверами, обращайтесь к стандарту SCSI[ANS] или к техническому руководству SCSI.
Ключ Описание 0x00 NO SENSE (НЕТ ОТВЕТА) 0x01 RECOVERED ERROR (ВСКРЫТАЯ ОШИБКА) 0x02 NOT READY (НЕ ГОТОВ) 0x03 MEDIUM ERROR (СРЕДНЯЯ ОШИБКА) 0x04 HARDWARE ERROR (ОШИБКА АППАРАТНОГО ОБЕСПЕЧЕНИЯ) 0x05 ILLEGAL REQUEST (НЕПРАВИЛЬНЫЙ ЗАПРОС) 0x06 UNIT ATTENTION (ПРЕДУПРЕЖДЕНИЕ) 0x07 DATA PROTECT (ЗАЩИЩЕННАЯ ИНФОРМАЦИЯ) 0x08 BLANK CHECK (ПРОВЕРКА НА ОТСУТСТВИЕ ИНФОРМАЦИИ) 0x09 (Vendor specific error) (Ошибка инициатора) 0x0a COPY ABORTED (ПРЕКРАЩЕННОЕ КОПИРОВАНИЕ) 0x0b ABORTED COMMAND (ПРЕКРАЩЕННАЯ КОМАНДА) 0x0c EQUAL (ЭКВИВАЛЕНТНОСТЬ) 0x0d VOLUME OVERFLOV (ПЕРЕПОЛНЕНИЕ) 0x0e MISCOMPARE (НЕСООТВЕТСТВИЕ) 0x0f RESERVED (ЗАРЕЗЕРВИРОВАНО) Таблица 3.1. Значения смысловых ключей.
TEST UNIT READY
Эта команда для тестирования статуса цели. Если цель может воспринимать команды среднего доступа (READ, WRITE),команда возвращает статус GOOD, в ином случае возвращается статус CHECK CONDITION и смысловой ключ NOT READY. Последнее обычно говорит о происходящем в настоящий момент самотестировании цели.
INQUIRY
Эта команда возвращает модель, производителя и тип устройства цели. Высокоуровневый Linux использует эту команду для определения разницы между оптическими, магнитными дисками и стримерами (высокоуровневый Linux не управляет принтерами, процессорами, или автоматическими устройствами).
READ и WRITE
Эти команды передачи информации от и к цели. До использования READ и WRITE вы должны убедиться в том, что ваш драйвер обладает возможностью поддержки простейших команд, таких, как TEST UNIT READY и INQUIRY.
Листание (paging)
Листание (paging) оперирует со страницей в отличии от подкачки (swapping), которая используется в отношении любых процессов. Мы будем использовать здесь термин "подкачка" для того, чтобы ссылаться на листание, т.к. Linux только листает и не замещает, но более привычным является термин "замещать" (swap), чем "листать" (page). Страницы ядра никогда не замещаются. Очищенные страницы также не читаются для замещения. Они освобождаются и перезагружаются, когда требуется. Программа подкачки поддержи вает один бит информации старения, являющимся битом PAGE_ACCESSED во входах таблицы страниц.
Linux использует множество файлов подкачки или устройства, которые могут быть включены и выключены посредством системных вызовов swapon и swapoff. Каждый файл подкачки или устройство описано посредством struct swap_info_struct (swap.c)
static struct swap_info_struct { unsigned long flags; struct inode *swap_file; unsigned int swap_device; unsigned char *swap_map; char *swap_lockmap; int lowest_bit; int highest bit; } swap_info[MAX_SWAPFILES];
Поле флагов (SWP_USED или SWP_WRITEOK) используется для управления доступом к файлам подкачки. Когда флаг SWP_WRITEOK сброшен, для этого файла не будет выделяться пространство. Это используется системным вызовом swapoff, когда он делает невозможным использование файла. Когда вызов swapon добавляет новый файл подкачки, то устанавливается SWP_USED. Статическая переменная nr_swapfiles содержит количество активных файлов подкачки. Поля lowest_bit и highest_bit связывают свободные области в файле подкачки и используются, чтобы увеличить скорость поиска свободной области подкачки.
Программа пользователя mkswap инициализирует устройство подкачки или файл. Первая страница включает заголовок ("SWAP-SPACE") в последних 10 байтах и содержит область битмап. Первоначальные нулевые значения в битмап сигнализируют о плохих страница х. "1" в битмап означает, что соответствующая страница свободна. Такой странице память никогда не выделяется, таким образом сразу необходимо провести инициализацию.
Системный вызов swapon() осуществляется из программы пользователя обычно из /etc/rc. Пара страниц памяти выделяется для swap_map и swap_lockmap.
swap_map содержит один байт на каждую страницу файла подкачки. Инициализируется из битмап и содержит 0 для допустимых страниц и 128 для неиспользуемых страниц. Используется для поддержки счета запросов на замещение каждой страницы в файле подкачки. swap_lockmap содержит один бит на каждую страницу, который используется, чтобы гарантировать взаимное исключение при чтении или записи в файл подкачки.
Когда страница памяти готова к тому, чтобы быть замещенной, индекс области замещения получается путем вызова get_swap_page(). Этот индекс затем загружается в биты 1-31 входа таблицы страниц, таким образом замещаемая страница может быть размещена, когда это необходимо, программой контроля корректности использования страниц do_no_page().
Верхние 7 битов индекса дают файл подкачки (или устройство), а нижние 24 бита дают номер страницы на этом устройстве. Это позволяет создавать до 128 файлов, каждый из которых предоставляет область до 64GB, но пространство свыше этого требует, что бы swap_map было бы большим. Вместо этого размер файла подкачки ограничивается 16MB потому, что swap_map тогда занимает 1 страницу.
Функция swap_duplicate() используется в copy_page_tables(), чтобы разрешить дочернему процессу наследовать замещаемые страницы во время распараллеливания. Это сразу увеличивает значение счетчика для этой страницы, поддержанного в swap_map. Каждый процесс будет замещен в отдельной копии страницы при обращении к ней.
swap_free() уменьшает счетчик, поддерживаемый в swap_map. Когда счетчик сбрасывает в 0, страница может быть перезагружена с помощью get_swap_page(). Эта функция вызывается каждый раз, когда замещаемая страница читается в память (swap_in()) или когда страница готова к тому, чтобы быть сброшенной (free_one_table() и т.п.).
Макросы, используемые при установке дескрипторов
В sched.h и system.h определены несколько ассемблерных макросов для простого доступа и установки дескрипторов. Каждый вход TSS и LDT занимает 8 байт.
Манипулирование GDT дескрипторами системы:
_TSS(n)
_LDT(n) предоставляют индекс в GDT для n-ой задачи. _LDT(n) загружается в поле ldt структуры tss_struct при распараллеливании. _set_tssldt_desc(n, addr, limit, type)
ulong*n указывает на вход GDT для установки (см. fork.c).
База сегмента (TSS или LDT) устанавливается в 0хс0000000 + addr. Вот специфичные экземпляры описанного выше, где ltypy ссылается на байт, содержащий P, DPL, S, и тип:
set_ldt_desc(n,addr) ltype=0x82
P=1, DPL=0, S=0, type=2 означает вход LDT. limit=23=? пространство для 3 дескрипторов сегмента
set_ldt_desc(n,addr) ltype=0x89
P=1, DPL=0, S=0, type=9 означает доступный 80386 TSS limit=231 пространство для 1 tss_struct
load_TR(n), load_ldt(n) загружает дескрипторы для задачи с номером n в регистр задачи и ldt регистр. ulong get_base(struct desk_struct ldt) берет базу из дескриптора ulong get_limit (ulong segment) берет ограничение (размер) из селектора сегмента. Возвращает размер сегмента в байтах. set_base(struct desk_struct ldt, ulong base), set_base(struct desk_struct ldt, ulong limit)
Установит базу и ограничения для дескрипторов (4К неделимых сегментов). Ограничением здесь является действительно существующий размер сегмента в байтах. _set_seg_desc(gate_addr, type, dpl, base, limit)
Значения по умолчанию 0х00408000 = ? D=1,P=1,G=0 В данный момент рабочий размер 32 бита и максимальный размер 1М. gate_addr должен быть (ulong*)
Механизм кеширования буфера.
Здесь следовало бы об'яснить, как вызывается ll_rw_block(), рассказать о getblk(), bread() и breada(), bwrite(). Подробное об'яснение механизма кеширования буфера отложено до создания описания VFS.
Читателю предлагается изучить его самостоятельно. Если у вас возникнут трудности, обращайтесь за помощью к автору этой книги.
Механизмы замораживания и активизации.
Начнем с об"яснения механизма заморозки и его использования. Это включает в себя то, что процесс, будучи в замороженном состоянии (не функционирует), в какой - то момент времени можно активизировать, а затем опять заморозить (приостановить )!
Возможно, лучший способ понять механизм замораживания и активизации в Linux - изучение исходного текста функции __sleep_on(), использующейся для описания функций sleep_on() и interruptible_sleep_on().
static inline void __sleep_on(struct wait_queue **p, int state) { unsigned long flags; struct wait_queue wait = { current, NULL };
if (!p) return; if (current == task[0]) panic ( "task[0] trying to sleep"); current->state = state; add_wait_queue(p, &wait); save_flags(flags); sti(); schedule(); remove_wait_queue(p, &wait); restore_flags(flags); }
wait_queue - циклический список указателей на структуры задач, определенные в как
struct wait_queue { struct task_struct * task; struct wait_queue * next; };
Меткой состояния процесса в данном случае является или TASK_INTERRAPTIBLE, или TASK_UNINTERRAPTIBLE, в зависимости от того, может ли заморозка процесса прерываться такими вещами, как системные вызовы.Вообще говоря, механизм заморозки необходимо прерывать лишь в случае медленных устройств, так как такое устройство может приостановить на достаточно длительный срок работу всей системы. add_wait_queue() отключает прерывание, создает новый элемент структуры wait_queue, определенной в начале функции как список p.Затем она восстанавливает в исходное положение метку о состоянии процесса.
save_flags() - макрос, сохраняющий флаги процессов, задаваемых в виде аргументов. Это делается для фиксации предыдущего положения метки состояния процесса. Таким образом, функция restore_flags() может восстанавливать положение метки.
Функция sti() затем разрешает прерывания, а schedule() выбирает для выполнения следующий процесс. Задача не может быть избранной для выполнения, пока метка не будет находиться в состоянии TASK_RUNNING.
Это достигается с помощью функции wake_up(),примененной к задаче, ждущей в структуре p своей очереди.
Затем процесс исключает себя из wait_queue,восстанавливает состояние положения прерывания с помощью restore_flags() и завершает работу.
Для определения очередности запросов на ресурсы в структуру wait_queue введены указатели на задачи, использующие этот ресурс. В таком случае, когда несколько задач запрашивают один и тот же ресурс одновременно, задачи, не получившие доступ к ресурсу, замораживаются в wait_queue.По окончании работы текущей задачи активизируется следующая задача из wait_queue,относящаяся к этому ресурсу с помощью функций wake_up() или wake_up_interruptible().
Если вы хотите понять последовательность разморозки задач или более детально изучить механизм заморозки, вам нужно купить одну из книг, предложенных в приложении А и просмотреть !mutual exclusion! и !deadlock!.
Написание драйвера SCSI.
Copyright 1993 Rickard E. Faith(). Все пpава заpезеpвиpованы.
Пpедоставляется пpаво pаспpостpанения и создания копиий этого документа, если пpимечание об автоpских пpавах и это pазpешение сохpаняется на всех копиях. Здесь пpедставлена (с позволения автоpа) модифициpованная копия оpигинального документа. Если вы желаете воспpоизводить лишь эту часть книги, вы можете получить оpигинал по адpесу ftp.cs.unc.edu:/pub/faith/papers/scsi.paper.tar.gz
Наставление читателю.
В этой части приводятся некоторые полезные при прочтении вещи.
Статические переменные.
Всегда определяйте статические переменные. Масса почти случайных ошибок возникает из - за игнорирования статических переменных. Т.к. ядро на самом деле не является запускаемой программой, сегмент bss не всегда обнуляется в зависимости от метода загрузки.
Невозможность использования libc.
Библиотека libc не доступна в ядре, однако некоторые функции из нее были продублированы. Смотрите разделы книги, в которых эти функции документированы. В основном, это разделы 3 и 9.
/\ Linux - это не UNIX.
\/ Linux - система, написанная не для коммерческого распространения.
|
Необходимые знания для изучения книги.
Для того, чтобы понять эту книгу, вы должны хотя бы поверхностно знать Си. Это означает, что вы можете читать программы на Си, не уделяя внимания справочникам. Вы должны уметь писать простейшие программы на Си и понимать структуры, указатели и макросы, атак же прототипы ANSI C.
Вы должны также представлять тексты стандартной библиотеки ввода/вывода, так как стандартные библиотеки не доступны в ядре. Некоторые часто используемые функции ввода/вывода были переписаны внутри ядра и они по возможности описываются далее. Также вам нужен хороший текстовый редактор, перекомпилирование ядра Linux и умение выполнять простейшие задачи системного администратора, такие как включение информации в /dev/.
Область имени (именная область).
Первое что вы должны сделать при написании драйвера - назвать устройство. Имя должно выть кратким - строка из двух - трех символов. К примеру, параллельные устройства - "lp", дисководы "fd", диски SCSI - "sd".
Создавая ваш драйвер, называйте функции в нем с первыми тремя буквами избранной строки в имени. Так как мы называем его foo - функции в нем соответственно - foo_read и foo_write.
Основы драйверов устройств.
Мы будем полагать, что вы не хотите писать драйвер на пользовательском уровне, а желаете работать непосредственно в области ядра.
В таком случае вам придется иметь дело с файлами .с и .h. Мы будем условно обозначать ваши труды как foo.c и foo.h.
Память пользовательского процесса
0xc0000000 невидимое ядро не используется начальный стек место для расширения стека 4 страницы 0x60000000 стабильно записанные биьлиотеки brk не используется распределенная память end_data не инициализированные данные end_code инициализированные данные 0x00000000 текст
Как сегмент кода, так и сегмент данных в каждом случае размещаются в области от 0х00 - 3GB. Обычно программа контроля равильности использования страниц do_wp_page проверяет, чтобы процесс не производил запись в область кода. Однако, если перехватить SEGV сигнал, то появляется возможность писать в область кода, инициируя возникновение COPY-ON-WRITE. Программа управления do_no_page гарантирует, что ни одна новая страница, выделенная процессу, не будет принадлежать ни исполняемой области, ни разделяемой библиотеке, ни стеку, ни попадет внутрь brk значения. Пользовательский процесс может сбросить свое brk значение посредством вызова sbrk(). Это то, что делает malloc(), когда это необходимо. Части текста и данных размещаются в отдельных страницах, если не установлена опция -N компилятора. Адреса загрузки разделяемой библиотеки обычно сами берутся из разделяемого пространства. Такой адрес лежит между 1.5GB и 3GB за исключением особых случаев.
своппируемая стабильная
страницы кода Y Y страницы информации Y N? стек Y N pg_dir N N кодовая/информационная page_table N N стек page_table N N task_struct N N kernel_stack_frame N N shlib page_table N N несколько shlib страниц Y Y?
[Что означают отметки в виде вопроса? Должны ли они означать вашу неуверенность или альтернативность решения?]
Стек, разделяемые библиотеки и данные слишком удалены друг от друга, чтобы перекрываться одной таблицей страниц. Все page_tables ядра разделяются всеми процессами и поэтому не приведены в списке. Перемещаются только грязные страницы. Чистые страницы могут заниматься, таким образом процесс может читать их снова из исполняемой области в случае такой необходимости. Обычно разделяются только чистые страницы. Грязная страница перестает разделяться в случае распараллеливания пока родитель или потомок не станет снова производить в нее запись.
Пpогpамиpование файловой системы /proc.
Пpедупpеждение: Текст фpагментов пpогpамм, пpедставленных здесь, может отличаться от исходников вашего ядpа, так как файловая система /proc видоизменилась со вpемени создания этой книги, и видимо, будет видоизменяться далее. Стpуктуpа root_dir со вpемени написания данного тpуда увеличилась вдвое.
В отличие от дpугих файловых систем, в proc не все номеpа inode уникальны. Некотоpые файлы опpеделены в стpуктуpах
static struct proc_dir_entry root_dir[] = { { 1,1,"." }, { 1,2,".." }, { 2,7,"loadavg" }, { 3,6,"uptime" }, { 4,7,"meminfo" }, { 5,4,"kmsg" }, { 6,7,"version" }, { 7,4,"self" }, /* смена номеpа inode */ { 8,4,"net" } };
Hекотоpые файлы динамически создаются во вpемя чтения файловой системы. Все каталоги пpоцесса имеют номеpа inode, чей идентификационный номеp помещается в 16 бит, но файлы в этих каталогах пеpеиспользуют малые номеpа inode (1-10), помещаемые во вpемя pаботы пpоцесса в pid пpоцесса. Это пpоисходит в inode.c с помощью аккуpатного пеpеопpеделения стpуктуp inode_operations.
Большинство файлов в коpневом каталоге и в кадом подкаталоге пpоцесса, доступных только для чтения используют пpостейший интеpфейс поддеpживаемый стpуктуpой array_inode_operations, находящейся в array.c.
Такие каталоги, как /proc/net, имеют свой номеp inode. К пpимеpу сам каталог net имеет номеp 8. Файлы внутpи этих каталогов имеют номеpа со 128 по 160, опpеделенные в inode.c и для пpосмотpа и записи таких файлов нужно специальное pазpешение.
Внесение файла является несложной задачей, и остается в качестве упpажнения читателю. Если пpедположить, что каталог в котоpый вносится файл не динамический, как к пpимеpу каталоги пpоцессов, пpиведем следующий алгоpитм:
Выбеpите уникальный диапазон номеpов inode, дающий вам пpиемлимый участок памяти для помещения. Зытем спpава от стpоки:
if (!pid) {/* в каталоге /proc/ */ сделайте запись идентичную следующей if ((ino>=128) && (ino<=160) { /*Файлы внутpи /proc/net*/ inode->i_mode = S_IFREG | 0444 inode->i_op = &proc_net_inode_operations; return; }
изменив ее для опpеpации нужной вам. В частности, если вы pаботаете в диапазоне 200-256 и ваши файлы имеют номеpа inode 200,201,202, ваши каталоги имеют номеpа 204 и 205, а номеp inode 206 имеет имеющийся у вас файл читаемый лишь из коpневpго каталога, ваша запись будет выглядеть следующим обpазом:
if ((ino >= 200)&&(ino <= 256)) { /* Файлы в /poc/foo */ switch (ino) { case 204: case 205: inode->i_mode = S_IFDIR | 0555; inode->i_op = &proc_foo_inode_oprations; break; case 206: inode->imode = S_IFREG | 0400; inode->i_op = &proc_foo_inode_operations; break; default: inode->i_mode = S_IFREG | 0444; inode->i_op = &proc_foo_inode_operations; break; } return; }
Hайдите место опpеделения файлов. Если ваши файлы помещаются в подкатаог каталога /proc, вам надо найти следующие стpоки в файле root.c:
static struct proc_dir_entry root_dir[] = { { 1,1,"." }, { 1,2,".." }, { 2,7,"loadavg" }, { 3,6,"uptime" }, { 4,7,"meminfo" }, { 5,4,"kmsg" }, { 6,7,"version" } { 7,4,"self" }, /* смена inode */ { 8,4,"net" } };
Затем вам следут подставить в эту запись после стpоки:
{ 8,4,"net" }
подставиь стpоку:
{ 9,3,"foo"}
Таким обpазом, вы пpедусматpиваете новый каталог в inode.c, и текст:
if (!pid) { /* not a process directory but in /proc/ */ inode->i_mode = S_IFREG | 0444; inode->i_op = &proc_array_inode_operations; switch (ino) case 5: inode->i_op = &proc_array_inode_operations; break; case 8: /* for the net directory */ inode->i_mode = S_IFDIR | 0555; inode->i_op = &proc_net_inode_operations; break; default: break; return; }
становится
if (!pid) { /* not a process directory but in /proc/ */ inode->i_mode = S_IFREG | 0444; inode->i_op = &proc_array_inode_operations; switch (ino) case 5: inode->i_op = &proc_array_inode_operations; break; case 8: /* for the net directory */ inode->i_mode = S_IFDIR | 0555; inode->i_op = &proc_net_inode_operations; break; case 9: /* for the foo directory */ inode->i_mode = S_IFDIR | 0555; inode->i_op = &proc_foo_inode_operatlons; break; default: break; return; }
Затем вам нужно обеспечить содеpжание файла в каталоге foo. Создайте файл proc/foo.c следуя указанной модели.
* linux/fs/proc/foo.c * * Copyright (C) 1993 Lunus Torvalds, Michael K. Johnson, and Your N. Here * * proc foo directory handling functions * * inode numbers 200 - 256 are reserved for this directory * (/proc/foo/ and its subdirectories) */
#include #include #include #include #include
static int proc_readfoo(struct inode *, struct file *, struct dirent *, int); static int proc-lookupfoo(struct inode *,const char *,int,struct inode **); static int proc_read(struct inode * inode, struct file * file, char * buf, int count), static struct file_operations proc_foo_operations = { NULL, /* lseek - default */ proc_read, /* read */ NULL, /* write - bad */ proc_readfoo, /* readdir */ NULL, /* select - default */ NULL, /* ioctl - default */ /* danlap */ NULL, /* mmap */ NULL, /* no special open code */ NULL /* no special release code */ };
/* * proc directories can do almost nothing.. */ struct inode_operations proc_foo_inode_operations = { &proc_foo_operations, /* default foo directory file-ops */ NULL, /* create */ proc_lookupfoo, /* lookup */ NULL, /* link */ NULL, /* unlink */ NULL, /* symlink */ NULL, /* mkdir */ NULL, /* rmdir */ NULL, /* mknod */
NULL, /* rename */ NULL, /* readlink */ NULL, /* follow_link */ NULL, /* bmap */ NULL, /* truncate */ NULL /* permission */ } ;
static struct proc_dir_entry foo_dir[] = { { 1,2,".." }, { 9,1,"." }, { 200,3,"bar" }, { 201,4,"suds" }, { 202,5,"xyzzy" }, { 203,3,"baz" }, { 204,4,"dir1" }, { 205,4,"dir2'' }, { 206,8,"rootfile" } };
#define NR_FOO-DIRENTRY ((sizeof (foo_dir))/(sizeof (foo_dir[0])))
unsigned int get_bar(char * buffer); unsigned int get_suds(char * buffer); unsigned int get_xyzzy(char * buffer); unsigned int get_baz(char * buffer); unsigned int get_rootfile(char * buffer);
static int proc_read(struct inode * inode, struct file * file, char * buf, int count) { char * page; int length; int end; unsigned int ino;
if (count < 0) return -EINVAL; page = (char *) get_free_page(GFP-KERNEL); if (!page) return -ENOMEM; ino = inode->i_ino; switch (ino) { case 200: length = get_bar(page); break; case 201: length = get_suds(page); break; case 202: length = get_xyzzy(page); break; case 203: length = get_baz(page); break; case 206: length = get_rootfile(page); break; default: free_page((unsigned long) page); return -EBADF; } if (file->f_pos >= length) { free_page ((unsigned long) page); return 0; } if (count + file->t_pos > length) count = length - file->f_pos; end = count + file->f_pos; memcpy_tofs(buf, page + file->f_pos, count); free_page((unsigned long) page); file->f_pos = end; return count;
}
static int proc_ lookupfoo(struct inode * dir, const char * name, int len, struct inode ** result) { unsigned int pid, ino; int i;
*result = NULL; if (!dir) return -ENOENT; if (!S_ISDIR(dir->i_mode)) { iput(dir); return -ENOENT; } ino = dir->i_ino; i = NR_FOO_DIRENTRY; while (i-- > 0 && !proc_match(len,name,foo_dir+i)) /* nothing */; if (i < 0) { iput(dir); return -ENOENT; } if (!(*result = iget(dir->i_sb,ino))) { iput(dir); return -ENOENT; } iput(dir); return 0; }
static int proc_readfoo(struct inode * inode, struct file * flie, struct dirent * dirent, int count)
{ struct proc_dir_entry * de; unsigned int pid, ino; lnt i,j;
if (!inode !S_ISDIR(inode->i_mode)) return -EBADF; ino = inode->i_ino; if (((unsigned) filp->f_pos) < NR_FOO_DIRENTRY) { de = foo_dir + filp->f_pos; filp->f_pos++; i = de->namelen; ino = de->low_ino; put_fs _long(ino, &dirent->d_ino); put_fs_word(i, &dirent->d_reclen); put_fs_byte(0, i+dirent->d_name); j = i; while (i--) put_fs_byte(de->name[i], i+dirent->d_name); return j; } return 0; }
unsigned int get_foo(char * buffer)
{ /* code to find everything goes here */
return sprintf(buffer, "format string ", variables); }
unsigned int get_suds(char * buffer) { /* code to find everything goes here */
return sprintf(buffer, "format string", variables); }
unsigned int get_xyzzy(char * buffer) { /* code to find everything goes here */
return sprintf(buffer, "format string", valriables); }
unsigned int get_baz(char * buffer) { /* code to find everything goes here */
return sprintf(buffer, "format string", variables); }
unsigned int get_rootfile(char * buffer) { /* code to find everything goes here */
return sprintf(buffer, "format string", variables); }
Пpмечание: Текст функций proc_lookupfoo() и proc_readfoo() абстактный, так как они могут использоваться в pазных местах. Заполнеие каталогов dir1 и dir2 остается в качестве упpажнения. В большинстве случаев эти каталоги не используются, однако алгоpитм пpедставленный здесь может быть пеpестpоен в pекpсивный алгоpитм заполнения более глубоких каталогов. Заметим, что в пpогpамме сохpанены номеpа inode с 200 по 256 для каталога /proc/foo/ и всех его подкаталогов, так что вы можете использовать незанятые номеpоа inode в этом диапазоне для ваших собственных файлов в dir1 и dir2. Пpогpамма pезеpвиpует диапазон под каждый каталог для ваших будующих pасшиpений. Автоp также пpедпочел собpать всю инфоpмацию и тpебуемые функции в foo.c нежели создавать дpугой файл, если файлы не в dir1 и в dir2 не сильно концептуально отличаются от foo. Сделайте соответствующие изменения в fs/proc/имя_файла. Это также будет достойным упpажнением для читателя. Пpимечание: вышенаписанная пpогpамма (/proc/net/supprt)была написана по памяти и может оказаться неполной. Если вы обнаpужите в ней какие-то несоответствия пожалуйста пpишлите аннотацию по адpесу johnsonm@sunsite.unc.edu.
Прерывание или поочередное опрашивание устройств ?
Аппаратное обеспечение работает достаточно медленно. Это определяется временем получения информации, в момент получения которой процессор не занят, и находится в состоянии ожидания. Для того чтобы вывести процессор из режима работа - ожидание, вводятся ! прерывания ! - процессы, предназначенные для прерывания конкретных операций и предоставления ОС задачи по выполнению которой последняя без потерь возвращается в исходное положение.
В идеале все устройства должны обрабатываться с использованием прерываний, однако на PC и совместимых прерывания используются лишь в некоторых случаях, так что некоторые драйверы вынуждены проверять аппаратное обеспечение на готовность к приему информации.
Так же существуют аппаратные средства ( дисплей с распределенной памятью ) работающие быстрее остальных частей компьютера. В таком случае драйвер, управляемый прерываниями будет выглядеть нелепо.
В Linux cуществуют как драйверы, управляемые прерываниями так и драйверы, не использующие прерываний, и оба типа драйверов могут отключаться или включаться во время работы подпрограммы. В частности, "lp" устройство ждет готовности принтера к принятию информации и, в случае отказа, отключается на какой-то промежуток времени, чтобы затем попытаться вновь.
Это улучшает показатели системы. Однако, если вы имеете параллельную карту, поддерживающую прерывания, драйвер, используя ее, увеличит скорость работы. Существуют несколько программных отличий между драйвером, управляемым прерываниями и ждущими драйверами. Для осознания этих отличий вы должны представлять себе устройство системных вызовов UN*X. Ядро - неразделяемая задача под UN*X. В таком случае в каждом процессе находится копия ядра.
Когда процесс запускает системный запрос, он не передает управление другому процессу, а скорее меняет режим исполнения на режим ядра. В этом режиме он запускает он запускает защищенный от ошибок код ядра.
В режиме ядра процесс все еще имеет доступ к пространству памяти пользователя, как и до смены режима, что достигается с помощью макросов: get_fs_*() и memcpy_fromfs(), осуществляющих чтение из памяти, и put_fs_*() и memcpy_tofs(), осуществляющих запись. Так как процесс переходит из одного режима в другой, вопроса о помещении информации в определенную область памяти не возникает.
Однако во время работы прерывания может работать любой процесс и вышеназванные макросы не могут быть использованы - они либо запишут информацию в случайную область памяти,либо повергнут ядро в ужас.
! Об'ясните, как работает verify_area(), который используется лишь в случае необусловленной защиты от записи во время работы в режиме ядра для проверки области памяти, принимающей информацию.!
Вместо отслеживания прерываний драйвер может выделять временную область для информации.Когда часть драйвера, управляемая прерыванием, заполняет эту область, она замораживает процесс,списывает информацию в пространство памяти пользователя.В блочных устройствах драйвер, создающий эту временную область, снабжен механизмом кеширования, что не предусмотрено в символьных устройствах.
Прерывания или последовательный вызов ?
В драйверах, не использующих прерывания, легко пишутся функции foo_read() и foo_write() :
static int foo_write(struct inode * inode, struct file * file, char * buf, int count) { unsigned int minor = MINOR(inode->i_rdev); char ret; while (count > 0) { ret = foo_write_byte(minor); if (ret < 0) { foo_handle_error(WRITE, ret, minor); continue; } buf++ = ret; count-- } return count; }
foo_write_byte() и foo_handle_error() - функции, также определенные в foo.c или псевдокоде.
WRITE - константа или определена #define.
Из примера также видно как пишется функция foo_read(). Драйверы, управ- ляемые прерываниями, более сложны :
Пример foo_write для драйвера, управляемого прерываниями :
static int foo_write(struct inode * inode, struct file * file, char * but, int count) { unsigned int minor = MINOR(inode->i_rdev); unsigned long copy_size; unsigned long total_bytes_written = 0; unsigned long bytes_written; struct foo_struct *foo = &foo_table[minor];
do { copy_size = (count <= FOO_BUFFER_SIZE ? count : FOO_BUFFER_SIZE); memcpy_fromfs(foo->foo_buffer, buf, copy_size);
while (copy_size) { /* запуск прерывания */
if (some_error_has_occured) { /* обработка ошибочного состояния */ }
current->timeout = jiffies +FOO_INTERRUPT_TIMEOUT; /* set timeout in case an interrupt has been missed */ interruptible_sleep_on(&foo->foo_wait_queue); bytes_written = foo->bytes_xfered; foo->bytes_written = 0; if (current->signal & ~current->blocked) { if (total_bytes_written + bytes_written) return total_bytes_written + bytes_written; else return -EINTR; /* nothing was written, system call was interrupted, try again */ } } total_bytes_written += bytes_written; buf += bytes_written; count -= bytes-written;
} while (count > 0);
return total_bytes_written; }
static void foo_interrupt(int irq) { struct foo_struct *foo = &foo_table[foo_irq[irq]];
/* Here, do whatever actions ought to be taken on an interrupt. Look at a flag in foo_table to know whether you ought to be reading or writing. */
/* Increment foo-> bytes_xfered by however many characters were read or written */ if (buffer too full/empty) wake_up_ interruptible(&foo->foo_wait_queue); }
Здесь функция foo_read также аналогична. foo_table[] - массив структур, каждая из которых имеет несколько элементов, в том числе foo_wait_queue и bytes_xfered, которые используются и для чтения, и для записи. foo_irq[] - - массив из 16 целых использующийся для контроля за приоритетами элементов foo_table[] засылаемыми в foo_interrupt().
Для указания обpаботчику пpеpываний вызвать foo_interrupt() вы должны использовать либо request_irq(), либо irqaction(). Это делается либо пpи вызове foo_open(), либо для пpостоты в foo_init(). request_irq() pаботает пpоще нежели irqaction и напоминает pаботу сигнального пеpеключателя. У нее существует два аpгумента:
номеp irq, котоpым вы pасполагаете>
указатель на пpоцедуpу упpавления пpеpываниями, имеющую аpгумент типа integer.>
request_irq() возвpащает -EINVAL, если irq > 15, или в случае указателя на пpогpамму pавного NULL, EBUSY если пpеpывание уже используется или 0 в случае успеха.
irqaction() pаботает также как функция sigaction() на пользовательском уpовне и фактически использует стpуктуpу sigaction. Поле sa_restorer() в стpуктуpе не используется, остальное - же осталось неизменным. См. pаздел "Функции поддеpжки" для более полной инфоpмации о irqaction().
В этой главе мы пытаемся
Обзор исходного текста ядра Linux.
В этой главе мы пытаемся по порядку об'яснить исходный текст ядра Linux, пытаясь помочь читателям понять, как структурирован исходный текст, и как описаны соответственные части Linux.Мы преследуем цель близко познакомить начинающего программиста на языке Си с общим устройством Linux.
В качестве отправной точки обзора возьмем загрузку системы. Для осознания этого материала требуются хорошее знание языка Си и практически полное представление о концепциях UN*X и архитектуре ПК.
В этой главе, однако, вы не встретите текстов на Си, а увидите лишь ссылки на истинный текст. Подробная информация о ядре находится в предыдущих главах, здесь же мы можем увидеть лишь обзорный материал.
Любой путь, встречающийся в этой главе, относится к основному кодовому дереву каталогов, обычно это /usr/src/linux.
Большинство информации, представленной в главе, взята из Linux 1.0, однако здесь встречаются ссылки на более поздние версии. Любой параграф в этой главе, выделенный так же, как этот, означает описание изменений в ядре по отношению к Linux 1.0. Если в параграфе нет подобных ссылок, это означает, что исходный текст не претерпел изменения в версиях 1.0.9 - 1.1.76. Иногда выделенные места появляются в тексте в качестве ссылок на исходный текст.
Преобразование мыши.
Если вы хотите написать драйвер, работающий так же, как и драйвер на уровне ядра, но не находящийся в его области, то вы можете создать fifo (буфер - first in, first out). Обычно он помещается в директорию /dev (во время нефункционирования) и ведет себя как подключенное устройство.
В частности, это используется когда вы используете мышь типа PS/2 и хотите запустить XFree86. Вы должны создать fifo, называемый /dev/mouse, и запустить программу mconv, которая, читая сигналы мыши PS/2 из /dec/psaux, пишет эквивалентные сигналы microsoft mouse в /dev/mouse.
В этом случае XFree86 будет читать сигналы из /dev/mouse и функционировать также как и при подключенной microsoft mouse.
Пример - vgalib.
Хорошим примером драйвера пользовательского уровня является библиотека vgalib. Стандартные функции read() и write() не подходят для написания действительно быстрого графического драйвера, и поэтому существует библиотека функций, которая концептуально работает как драйвер устройства, но на пользовательском уровне. Все функции, которые используют ее, должны запускать setuid, так как она использует системную функцию ioperm(). Функции, которые не запускают setuid, обладают возможностью записи в /DEV/MEM, если у вас есть группы mem или kmem, которые позволяют это, но только корневые процессы могут запускать ioperm().
Есть несколько портов ввода/вывода, относящихся к графике VGA. Vgalib дает им символические имена с помощью #define, и далее используют ioperm() для разрешения функции правильного прочтения и записи в эти порты.
if (ioperm(CRT_IC, 1, 1)) { printf("VGAlib: can't get I/O permission \n"); exit(-1); } ioperm(CRT_IM, 1, 1); ioperm(ATT_IW, 1, 1); [--]
Это требует лишь однократной проверки, так как единственной причиной нефункционирования ioperm() может быть обращение к ней не в статусе superuser или во время смены статуса.
/\
\/ После вызова этой функции разрешается использование inb и outb инструкций, однако лишь с определенными портами. Эти инструкции могут быть доступны без использования прямого ассемблерного кода , но работают они лишь в случае компиляции с параметром optimization on и с ключом -0?. Для более подробных сведений читай .
После обращения в порты ввода вывода vgalib засылает информацию в область ядра следующим образом :
/* open /dev/mem */ if ((mem_fd = open("/dev/mem", 0_RDWR) ) < 0) { prntf( "VGAlib: can' t open /dev/mem \n"); exit (-1); }
/* mmap graphics memory */ if ((graph_mem = malloc(GRAPH*SIZE + (PAGE-SIZE-1))) == NULL) { printf( " VGAlib: allocation error \n "); exit (-1); } if ((unsigned long)graph_mem % PAGE_SIZE) graph_mem += PAGE_SIZE - ((unsigned long)graph_mem % PAGE_SIZE); graph_mem = (unsigned char *)mmap( (caddr_t)graph_mem, GRAPH_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, mem_fd, GRAPH_BASE ); if ((long)graph_mem < 0) { printf(" VGAlib: mmap error \n"); exit (-1); }
В начале программа открывает /dev/mem, затем выделят достаточное количество памяти для распределения на страницу, затем меняет карту памяти.
GRAPHSIZE - размер памяти vga. GRAPHBASE - адрес начала памяти VGA в /dev/mem.
Затем, записывая в адрес возвращаемый mmap(), программа осуществляет запись в память экрана.
Процессы и программа управления памятью
Работа, связанная с памятью, производимая посредством fork():
Выделение памяти
1 страница для task_struct. 1 страница для стека ядра. 1 для pg_dir и несколько для pg_tables (co[py_page_tables)
Другие изменения
ss0 установить на адрес сегмента стека ядра (0х10) чтобы быть уверенным? esp0 установит на вершину вновь созданного kernel_stack_page cr3 устанавливается посредством copy_page_tables() для указания на вновь размещенную директорию страниц. ltd=_LTD(task_nr) создает новый дескриптор ltd. дескриптор устанавливается в gdt для нового tss и ltd[]. Остальные регистры наследуются от родителя.
Процессы прекращают разделение их сегментов данных и кода (хотя они имеют раздельные локальные таблицы дескрипторов, входы указывают на те же сегменты). Страницы стека и данных будут скопированы, когда родитель или наследник будет писать в них (СOPY-ON-WRITE)
Работа, связанная с памятью, производимая посредством exec():
Выделение памяти
1 страница для exec-заголовка полного файла для omagic 1 страница или больше для стека (MAX_ARG_PAGES)
clear_page_tables() используется для удаления старых страниц. change_ltd() устанавливает дескрипторы в новом LTD[] ltd[1] = code base = 0x00, limit = TASK_SIZE ltd[2] = data base = 0x00, limit = TASK_SIZE
Эти сегменты представляются DPL=3,P=1,S=1,G=1 type=a(code) или 2(data)
До MAX_ARG_PAGES грязные страницы argv и envp размещаются и сохраняются на вершине сегмента данных для вновь созданного пользовательского стека. Установить указатель инструкции вызывающей программы eip = ex.a_entry. Установить указатель стека вызывающей программы на созданный стек (esp = stack pointer) Данные будут выбраны из стека при возобновлении вызывающей программы. Редактирование ограничений на память
end_coe = ex.a_text
end_data = end_code + ex.a_data
brk = end_data + ex.a_bss
Программные и аппаратные прерывания управляются внутри контекста текущей задачи. Более детально, директория страниц текущего процесса используется при трансляции адреса. Сегменты, однако, являются сегментами ядра и таким образом все линейные адреса указывают в область памяти ядра. Предположим, например, что пользовательский процесс выполняет системный вызов и ядро хочет получить доступ к переменной по адресу 0х01. Линейный адрес будет равен 0хс0000001 (использование сегментов ядра) и физический адрес - 0х01. Буква здесь присутствует вследствие того, что директория страниц процесса отображает этот диапазон точно как page_pg_dir.
Область ядра (0хс0000000 + high_memory) отображается через таблицы страниц ядра, которые являются частью RESURVED памяти. Поэтому они разделяются всеми процессами. Во время распараллеливания copy_page_tables() напрямую обращается к таблицам RESERVED страниц. Она устанавливает указатели в директориях страниц процесса, чтобы указывать на таблицы страниц ядра и не размещает в памяти новых таблиц страниц, как это обычно делается. В качестве примера kernel_stack_page (который размещен где-то в области ядра) не нуждается в связанной page_table, размещенной в p_dir процесса, чтобы отобразить ее.
Инструкция прерывания устанавливает указатель стека и сегмент стека из привилегированных 0 значений, сохраненных в tss текущей задачи. Заметьте, что стек ядра в действительности является фрагментированным объектом - это не единственный объект, а группа стековых фреймов, каждый из которых размещается в памяти при создании процесса и освобождает память при окончании его. Стек ядра никогда не должен расти внутри контекста процесса слишком быстро, чтобы не расширится за пределы текущего фрейма.
Программы контроля корректности использования страниц
Когда процесс создается посредством распараллеливания, он стартует со своей директорией страниц и своей страницей. Таким образом программа контроля корректности использования страниц следит почти за всей памятью процесса.
Программа контроля do_page_fault() считывает некорректный адрес из регистра cr2. Код ошибки (считанный в sys_call.S) позволяет определить режим доступа - пользователя/супервизора и причину ошибки - защита записи или неправильная страница. Формирователь управляется do_wp_page() и позднее do_no_page().
Если нарушение адреса превышает TASK_SIZE, процесс получает SIGKILL. [Зачем этот контроль? Такое может произойти только в режиме ядра из-за защиты на уровне сегмента]
Эти процедуры обладают тонкостями т.к. они могут вызываться по прерыванию. Вы не можете предположить, что это текущая задача.
do_no_page() контролирует три возможные ситуации:
Страница замещается. Страница принадлежит исполняемой или разделяемой библиотеке. Страница некорректна - страница данных, которая не была загружена.
Во всех случаях в первую очередь вызывается get_empty_pgtable() чтобы гарантировано определить существование таблицы страниц, которая накрывает некорректный адрес. В случае 3 get_empty_page() вызывается чтобы обеспечить страницу с требуемым адресом и в случае замещаемой страницы вызывается swap_in().
В случае 2 программа контроля вызывает share_page(), чтобы посмотреть является ли страница разделяемой каким либо другим процессом. В случае неудачи она считывает страницу из исполняемой программы или библиотеки (Она повторяет вызов shre_page() в случае, если другой процесс делал тем временем то же самое). Любая часть страницы сверх значения brk обнуляется.
Считывание страницы с диска вычисляется как основная ошибка (mjr_flt). Это происходит с swap_in() или когда происходит считывание из выполняемой программы или библиотеки. Другие случаи интерпретируются как второстепенные ошибки (min_flt).
Когда найдена разделяемая страница, то она защищена для записи. Процесс, который пишет в разделяемую страницу, затем должен будет пройти через do_wp_page(), которая выполняет COPY-ON-WRIGHT.
do_wp_page() выполняет следующее:
посылает SIGSEGV, если какой-либо пользовательский процесс пишет в текущую code_space. Если старая страница не разделяется, она становится незащищенной. Иначе get_free_page() и copy_page(). Для страницы устанавливается грязный флаг из старой страницы. Уменьшается значение счетчика карты старой страницы.
С чего начинать ?
Авторы низкоуровневых драйверов устройств должны представлять себе, как управляет прерываниями ядро. Как минимум, вами должны быть изучены функции, которые разрешают (sti()) и запрещают(cli()) прерывания. Также для некоторых драйверов нужны функции определения времени вызова функций schedule(), sleepon() и wakeup(). В разделе 2.6 вы можете встретить более подробное описание этих функций.
Сегментные регистры используются при трансляции
Сегментные регистры используются при трансляции адреса для генерации линейного адреса из логического (виртуального) адреса. linear_address = segment_base + logical_address
Линейный адрес транслируется затем в физический адрес посредством аппаратуры страничной организации (paging)
Каждый сегмент в системе описан 8-ми байтным дескриптором сегмента, в котором содержится вся необходимая информация (база, ограничение, тип, привилегии).
Имеют место следующие сегменты:
Обычные сегменты
сегменты кода и данных
Системные сегменты
(TSS) cегменты состояния задачи
(LDT) таблицы локальных дескрипторов
Характеристики системных сегментов:
Системные сегменты являются спецификаторами задач Имеется TSS, связанный с каждой задачей в системе. Он содержит tss_struct (sched.h). Размер этого сегмента соответствует размеру tss_struct, исключая i387_union (232 байта). Он содержит всю информацию, необходимую для перезапуска задачи. Таблицы LDT содержат дескрипторы обычных сегментов, принадлежащих задаче. В Linux каждой задаче соответствует одна LDT. B Linux task_struct предусмотрено пространство для 32-х дескрипторов. Обычная LDT, созданная в Linux, имеет размер 24 байта и, следовательно, пространство для 3-х входов. Ее содержимое:
LDT[0] Null (принудительно) LDT[1] дескриптора сегмента пользовательского кода LDT[2] дескриптор сегмента пользовательских данных/стека
Все сегменты пользователя имеют базу 0х00, так что линейный адрес тот же самый, что и логический.
Для получения доступа ко всем этим сегментам 386 использует таблицу глобальных дескрипторов (GDT), которая устанавливается в памяти системой (местоположение задается регистром GDT). GDT содержит дескрипторы сегментов для каждого сегмента состоян ия задачи, каждого локального дескриптора и обычных сегментов. Linux GDT содержит входы двух обыкновенных сегментов:
GDT[0] нулевой дескриптор GDT[1] дескриптор сегмента кода ядра GDT[2] дескриптор сегмента данных/стека ядра
Оставшаяся область GDT заполнена TSS и LDT дескрипторами системы.
GDT[3] ??? GDT[4] = TSS0, GDT[5] = LDT0 GDT[6] = TSS1, GDT[7] = LDT1
..... и т.д......
Заметьте LDT[n] != LDTn
LDT[n] = n-й дескриптор в LDT текущей задачи LDTn = дескриптор в GDT для LDT n-й задачи
В данном случае GDT имеет 256 входов, пространство для 126 задач. Сегменты ядра имеют базу 0хс0000000, которая задает местонахождение ядра в линейном представлении. Прежде, чем сегмент может быть использован, содержимое дескриптора для этого сегмента должно быть загружено в сегментный регистр. 386 имеет множество сложных критериев, ограничивающих доступ к сегментам, так что вы не сможете просто загрузить дескриптор в сегментный регистр. Также эти сегментные регистры имеют невидимые для программиста участки. Видимые участки - это то, что обычно называется сегментными регистрами cs, ds, es, fs, gs и ss.
Программист загружает один из этих регистров 16-ти битным значением, называемым селектором. Селектор однозначно идентифицирует дескриптор сегмента в одной из таблиц. Доступ подтверждается и соответствующий дескриптор загружается посредством аппар атных средств.
Обычно в Linux игнорируется комплексная защита на уровне сегмента (чрезмерная?), предоставляемая 386. Она базируется на основе аппаратных средств страничной организации и объединенной защитой на уровне страницы. Правила на уровне сегмента, которые применяются к пользовательским процессам, состоят в следующем:
Процесс не может напрямую обращаться к данным ядра или сегментам кода. Всегда имеет место контроль ограничения, однако, условие, что каждый пользовательский сегмент размещается от 0х00 до 0хс0000000, неудобно для применения. [Это изменено и нуждается в редактировании]
чтобы через сегментный регистр задать
Селектор сегмента загружается в сегментный регистр (cs, ds и т.д.), чтобы через сегментный регистр задать один из обычных сегментов в системе как один адрес.
Формат селектора сегмента: 15 3 2 1 0 индекс TI RPL
TI - индикатор таблицы:
0 означает, что индексы селектора относятся к GDT 1 означает, что индексы селектора относятся к LDT
RPL - привилегированный уровень. Linux использует только два привилегированных уровня
0 означает ядро 1 означает пользователя
Примеры:
Сегмент кода ядра:
TI = 0, индекс = 1, RPL = 0 поэтому сектор = 0х08 (GDT[1])
Сегмент данных пользователя
TI = 1, индекс = 2, RPL = 3 поэтому сектор = 0х17 (LDT[2])
Cелекторы, используемые в Linux:
TI index RPL selector segment 0 1 0 0x08 код ядра GDT[1] 0 2 0 0x10 данные/стек ядра GDT[2] 0 3 0 ??? ??? GDT[3] 1 1 3 0x0F код пользователя LDT[1] 1 2 3 0x17 данные/стек пользователя LDT[2]
Селекторы для сегментов системы не предназначены для прямой загрузки в сегментные регистры. Напротив, должны быть загружены TR или LDTR.
На входе системного вызова:
ds и es устанавливаются на сегмент данных ядра (0х10) fs устанавливается на сегмент данных пользователя (0х17) и используется для доступа к данным, на которые указывают аргументы системного вызова Указатель на вершину и сегмент стека автоматически устанавливаются в ss0 по прерыванию и старые значения восстанавливаются при возврате из системного вызова.
Символьные и блочные устройства.
Существует два типа устройств в системах UN*X - символьные и блочные устройства. Для символьных устройств не предусмотрено буфера, в то время как блочные устройства имеют доступ лишь через буферную память. Блочные устройства должны быть равнодоступными, а для символьных это не обязательно, хотя и возможно. Файловая система может работать лишь в случае, если она является блочным устройством.
Общение с символьными устройствами осуществляется с помощью функций foo_read() и foo_write(). Функции foo_read() и foo_write() не могут останавливаться в процессе деятельности, поэтому блочные устройства даже не требуют использования этих функций, а вместо этого используют специальный механизм, называемый "strategy routine" - стратегическая подпрограмма. Обмен информацией происходит при помощи функций bread(), breada(), bwrite(). Эти функции, просматривая буферную память, могут вызывать "strategy routine" в зависимости от того, готово устройство или нет к приему информации (в случае записи - буфер переполнен), или же присутствует ли информация в буфере (в случае чтения ).Запрос текущего блока из буфера может быть асинхронен чтению - breada() может вначале определить график передачи информации, а затем заняться непосредственно передачей. Далее мы представим полный обзор буферной памяти(кэш). Исходные тексты для символьных устройств содержатся в /kernel/chr_drv, исходники для блочных - /kernel/blk_drv. Для простоты чтения интерфейсы у них довольно просты, за исключением функций записи и чтения. Это происходит из за определения вышеописанной "strategy routine" в случае блочных устройств и соответствующего ему определения foo_read и foo_write() для символьных устройств. Более подробно об этом в 2.4.1 и 2.5.1.
Стpуктуpа файловой системы /proc.
Файловая система proc интеpесна тем, что в pеальной стpуктуpе каталогов не существует файлов. Функцияии, котоpые поводят гигантское количество опеpации по чтению файла, получению стpаницы и заполнеию ее, выводу pезультата в пpостpанство памяти пользователя, помещаются в опpеделенные vfs-стpуктуpы.
Одним из интеpеснейших свойств файловой системы proc, является описание каталогов пpоцессов. По существу, каждый каталог пpоцесса имеет свой номеp inode своего PID помещеннающий 16 бит в 32 - битный номеp больше 0x0000ffff.
Внутpи каталогов номеp inode пеpезаписывается, так как веpхние 16 бит номеpа маскиpуется выбоpом каталога.
Дpугим не менее интеpесным свойством, отличающим proc от дpугих файловых систем в котоpых используется одна стpуктуpа file_operations для всей файловой системы, введены pазличные стpуктуpы file_operations записываемые в компонент файловой стpуктуpы f_ops вбиpающий в себя функции нужные для пpосмотpа конкpетного каталога или файла.
Существует два уровня косвенной адресации
Существует два уровня косвенной адресации при трансляции адреса в модуле страничной организации. Директория страниц содержит указатели на 1024 таблицы страниц. Каждая таблица страниц содержит указатели на 1024 страницы. Регистр CR3 содержит физический базовый адрес директории страницы и загружается как часть TSS в task_struct и поэтому загружается при каждом переключении задачи. 32-х битный линейный адрес разделяется следующим образом:
31 22 21 12 11 0 DIR TABLE OFFSET
Физический адрес затем вычисляется (аппаратно) таким образом:
CR3+DIR указатель на table_base table_base+TABLE указатель на page_base physical_address= page_base + OFFSET
Директории страниц (таблицы страниц) это страница, выровненная так, что нижние 12 бит используются для загрузки полезной информации о таблице страниц (страница), указатель на которую задается посредством входа. Формат входов директории страниц и таблицы страниц:
31 12 11 9 8 7 6 5 4 3 2 1 0 ADDRESS OS 0 0 D A 0 0 U/S R/W P
D - "1" означает, что страница грязная (неопределенно для входа директории страниц).
R/W - "0" означает для пользователя "только для чтения".
U/S - "1" означает страницу пользователя.
P - "1" означает, что страница находится в памяти.
А - "1" означает, что к странице был доступ (устанавливается в 0 при старении).
OS - биты могут использоваться для LRU и т.п. и определяются OS.
Соответствующие определения для Linux находятся в .
Когда страница замещается, используются биты 1-31 входа таблицы страниц, чтобы отметить, куда при замещении помещается страница (бит "0" должен иметь значение 0).
Страничная организация (paging) делается доступной путем установки старшего бита в CR0 [в head.S?]. В каждой фазе трансляции адреса проверяются разрешения доступа, страницы не присутствуют в памяти и нарушение защиты приводит к их отсутствию. Затем программа контроля корректности использования страниц (в memory.c) или вносит новую страницу, или снимает защиту страницы, или делает все необходимое, что должно быть сделано.
Информация о некорректной работе со страницей
Регистр CR2 содержит линейный адрес, в котором было вызвано нарушение страницы. Коды нарушения страницы (16 бит):
бит сброшен установлен 0 страница не существует защита уровня страницы 1 нарушение при чтении нарушение при записи 2 режим супервизора режим пользователя
Остальные биты не определены. Приведенная информация является выдержкой из sys_call.S
Translation Lookside Buffer (TLB) представляет собой аппаратную кэш-память для физических адресов, которые соответствуют ранее используемым виртуальным адресам. Когда транслируется виртуальный адрес, 386 в первую очередь просматривает TLB, чтобы узнать - является ли доступной необходимая информация. Если нет, то для того, чтобы получить страницу, он должен создать пару ссылок на память для доступа к директории страниц и затем таблице страниц. Три ссылки на физическую память для трансляции адрес а для каждой ссылки на логическую память убили бы систему и, следовательно, TLB.
TLB заполняется, если загружен CR3 или по переключению задач, в результате которого изменяетсяCR0. В Linux она заполняется путем вызова invalidate(), которая как раз и перезагружает CR3.
Strategy Routine.
Обработка блочных данных осуществляется strategy routine. Эта подпрограмма не имеет аргументов и ничего не возвращает, однако ей известно, где найти список запросов ввода/вывода (CURRENT определена как blk_dev[MAJOR_NR].current_request),а также как получать данные от устройства и формировать блоки. Она вызывается при !запрещенных ! прерываниях, так что для разрешения прерываний вам надо вызвать функцию sti() до возврата "strategy routine".
"Strategy routine" сначала вызывает макрос INIT_REQUEST,который убеждается в принадлежности запроса списку запросов. add_request() сортирует запросы в определенном порядке с помощью алгоритма elevator, вызываемого в связи с каждым запросом, так что "strategy routine" должна лишь удовлетворить текущий запрос, затем вызвать end_request(1) для удаления запроса и так далее,пока запросов в списке не останется.
В случае, если ваш драйвер управляется прерываниями, он, вызывая "strategy routine", передает ей конкретный запрос, прерывая работу компьютера, затем, после выполнения задачи, поставленной запросом, он исключает последний из списка с помощью end_request(), после чего в нужный момент, определяемый обработчиком прерываний, драйвер опять вызывает "strategy routine" со следующим процессом.
Если во время удовлетворения текущего запроса происходит сбой ввода/вывода, для снятия запроса также вызывается end_request().
Запрос может быть на чтение и запись. Драйвер определяет тип запроса, просматривая CURRENT -> cmd.
CURRENT -> cmd == READ - чтение, CURRENT -> cmd == WRITE - запись.
Если устройство имеет раздельные управляемые прерываниями подпрограммы чтения и записи, то драй вер должен использовать SET_INTR(n) для определения типа запроса.
!Здесь нужно привести пример strategy routine процедуры, не использующей прерывания и использующей их.Драйвер, управляемый прерываниями, будет заключать в себе раздельные процедуры ввода/вывода для указания, как использовать SET_INTR. !
Структура Scsi_Cmnd
Структура Scsi_Cmnd, как показано на рисунке 1.6 использует код высокого уровня для спецификации комманды SCSI для запуска низко-уровневым кодом. Множество переменных в структуре Scsi_Cmnd могут не использоваться в драйвере низкого уровня.
Структура Scsi_Host.
Структура Scsi_Host служит для описания драйвера низкого уровня коду высокого. Обычно это описание помещается в главный файл драйвера устройства в препроцессорные определения, как показано на рис. 1.1.
Структура Scsi_Host представлена на рис. 1.2 Каждое из полей будет дале подробно об'яснено.
#deflne FDOMAIN_16X0 { "Future Domain TMC-16x0", \ fdomain-16x0_detect, \ fdomain_16x0_info, \ fdomain_16x0_command, \ fdomain_16x0_queue, \ fdomain_16x0_abort, \ fdomain_16x0_reset, \ NULL, \ fdomain_16x0_biosparam, \ 1, 6, 64, 1,0, 0} #endif Рис 1.1: Основной файл драйвера устройства.
typedef struct { char *name; int (* detect) (int); const char *(* info)(void); int (* queuecommand)(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); int (* command) (Scsi_Cmnd *); int (* abort) (Scsi_Cmnd *, int); int (* reset) (void); int (* slave_attach) (int, int); int (* bios_param)(int, int, int []); int can_queue; int this_id; short unsigned int sg_tablesize; short cmd_per_lun; unsigned present:1; unsigned unchecked_isa_dma:1; } Scs i_Host; Рис.1.2: Структура Scsi_Host.
Типографские соглашения.
Выделенный шрифт используется для обозначения определений, предупреждений и ключевых слов в языке.
Курсив используется для обозначения вставок и введений для новых статей.
Наклонный шрифт используется для обозначения мета-переменных в тексте, особенно в командной строке:
ls - l [foo]
где [foo] - имя файла,как /bin/cp. Иногда довольно сложно в тексте заметить наклонный шрифт, и соответствующее выражение берется в [---].
Шрифт печатной машинки используется для отображения ответной информации компьютера:
ls -l /bin/cp [-rwxr-xr-x 1 root wheel 12104 Sep 25 15:53 /bin/cp]
также он используется для примеров в кодах Си для обозначения системных команд и для описания конфигурационных файлов. Иногда для наглядности эти примеры помещаются в рамку. В <---> скобки берется нажатая клавиша: Для продолжения нажмите .
Звездочка на полях выделяет место, требующее особого внимания.
в линейный адрес посредством аппаратных
Логический адрес, задаваемый в инструкции в первую очередь транслируется в линейный адрес посредством аппаратных средств сегментации. Этот линейный адрес затем транслируется в физический адрес модулем страничной организации (paging unit).
В.3 Как ядро рассматривает процесс.
С точки зрения ядра процесс есть ни что иное, как запись в таблице процессов. Таблица процессов - одна из важнейших структур данных внутри системы совместно с таблицей распределения памяти и механизмом кэширования буфера. Особое место в таблице процессов занимает довольно об'емная структура task_structure, определенная в include/linux/sched.h. Внутри структуры task_struct содержится информация как высокого, так и низкого уровня - от некоторых регистров аппаратного обеспечения до inode работающей директории процесса.
Таблица процессов является одновременно массивом и двусвязным списком в виде дерева. Физическое описание представляет собой статический массив указателей, длина которого NR_TASKS, является константой, определенной в include/linux/tasks.h, так что размер структуры может быть переопределен лишь на определенной зарезервированной странице. Структура списка определена двумя указателями next_task и prev_task, а структура дерева общеизвестна и мы не будем на ней здесь останавливаться. Вы можете изменить величину NR_TASKS со 128, как установлено по умолчанию, однако вам придется перекомпилировать все исходные файлы измененные при этом. После загрузки ядро работает от имени какого-либо процесса, используя глобальную переменную current и указатель на структуру task_struct для запуска прцессов. current изменятся только планировщиком в kernel/sched.c.
Когда надо закрыть все процессы, используется макрос for_each_task. Это намного быстрее нежели послеовательное считывание массива, когда система загружена неполностью.
Процесс работает одновременно и в режиме ядра, и в режиме пользователя. Оновная часть процесса запускается в пользовательском режиме, системные вызовы запускаются в режиме ядра. Стеки, используемые работающими в разных режимах процессами, различны - определенный сегментный стек используется в пользовательском режиме, а режим ядра использует стек определенной величины.
Стековая страница ядра никогда не своппится, так как она должна быть доступна в любое время работы системы.
Системные вызовы внутри ядра существуют как функции на языке Си и их имена начинаются с префикса "sys_". Системный вызов burnout, к примеру, содержится в ядре в качествефункции Sys_burnout().
Механизм обработки системных вызовов описан в главе 3 этой книги. Просмотр for_each_task и SET_LINKS в include/linux/shed.h может помочь вам в понимании структуры списка и дерева в таблице процессов.
VFS.
VFS - Virtual Filesystem Switch (Система виртуального переключения файловой системы ) - механизм, позволяющий Linux поддерживать сразу несколько файловых систем. В первой версии Linux доступ к файловой системе осуществляется через подпрограммы, работающие с файловой системой minix. Для обеспечения возможности работы с другой файловой системой ее вызовы переопределяются как функции знакомой Linux системы файлов. Это делается с помощью программы, содержащей структуру указателей на функции, представляющие все возможные действия с файловой системой. Вызывает интерес структура file_operations :
From /usr/include/linux/fs.h:
struct file_operations { int (*lseek) (struct inode *, struct file *, off_t, int); int (*read) (struct inode *, struct file *, char *, int); int (*write) (struct inode *, struct file *, char *, int); int (*readdir) (struct inode *, struct file *, struct dirent *, int count); int (*select) (struct inode *, struct file *, int, select_table *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned int); int (*mmap) (struct inode *, struct file *, unsigned long, size_t, int, unsigned long); int (*open) (struct inode *, struct file *); void (*release) (struct inode *, struct file *); };
Эта структура содержит список функций, нужных для создания драйвера.
Эта книга вдохновляет вас, начинающих
Эта книга вдохновляет вас, начинающих исследователей ядер, не достаточно знающих UNIX-системы, для изучения ядра Linux, когда она впервые появилась у вас и еще тяжела для полного понимания. Это пособие создано для того, чтобы помочь вам быстрее изучить основные концепции и выделить из внутренней структуры Linux то, что может понадобиться вам, чтобы, не читая полностью исходный текст ядра, определить, что же случилось с какой-либо конкретной переменной. Почему Linux ? Linux - это первая свободно доступная система типа UNIX для 386 компьютеров. Она была полностью переписана в уменьшенном объеме так, не имеет большого количества функций, работающих с режимом реального времени, как в других операционных системах (386BSD), и, следовательно, проста в понимании и доступна для изменений.
UNIX появился около 20 лет назад, но только недавно появились столь мощные микрокомпьютеры, способные поддерживать работу операционных систем с многозадачным, многопользовательским защищенным режимом. Кроме того, описания UNIX труднодоступны, лишь документация о внутренностях ядра распространялась свободно. UNIX, кажущийся в начале простым, со временем увеличивался в размерах и превратился в объемную систему, понятную лишь профессионалу. С Linux, однако, мы можем решить часть описанных выше проблем в связи с тем, что:
У Linux довольно простое ядро с хорошо структурированным интерфейсом;
Контроль за написанием ядра вел один человек - Linus Torvalds, что не позволило появиться в ядре раздробленным участкам;
Исходные тексты ядра свободно распространяются, так что начинающие программисты могут свободно понимать и изучать их, становясь выше в собственных глазах.
Мы надеемся, что эта книга поможет начинающим исследователям ядер разобраться в ядре Linux, поняв его структуру.
Сведения об авторских правах.
Авторские права на главу "Распределение памяти в Linux" принадлежат Krichna Balasubramanian. Некоторые изменения запатентованы Майклом К.Джонсоном и Дугласом Р.Джонсоном.
"Как система вызывает процедуру": авторскими правами на оригинал этой статьи обладает Stanley Scalsky.
"Написание драйвера устройства SCSI": авторскими правами обладает Ric Faith.
Для пользовательского процесса segment_base = 0x00, page_dir для процесса своя. процесс пользователя выполняет системный вызов: segment_base=0xc0000000 page_dir=та же самая page _dir пользователя swapper_pg_dir содержит распределение памяти для всех физических страниц от 0xc0000000 до 0xc0000000 + end_mem, так что первые 768 входов в swapper_pg_dir раны 0 и затем имеются 4 или более входов, которые указывают на таблицы страниц ядра Директории страниц пользователя имеют те же входы, как tt swapper_pg_dir свыше 768. Первые 768 входов представляют пространство пользователя.
В результате всегда, когда линейный адрес превышает 0xc0000000, используются те же таблицы страниц ядра.
Пользовательский стек размещается на вершине сегмента данных пользователя и увеличивается вниз. Стек ядра не является точным представлением структуры данных или сегмента, относительно которой я бы мог сказать "Вы находитесь в стеке ядра". Kernel_stack_frame (страница) связывается с каждым вновь создаваемым процессом и используется всякий раз, когда ядро выполняет действия с контекстом процесса. Неприятности могут произойти, если стек ядра будет расти ниже его текущего кадра.[Где размещен стек ядра? Я знаю, что для каждого процесса существует свой стек, но где он расположен, когда процесс не используется?]
Страницы пользователя могут заниматься или замещаться. Страница пользователя отражается ниже 3GB в таблице страниц пользователя. Эта область не содержит директорий страниц или таблиц страниц. Меняются местами (замещаются) только грязные страницы.
Необходимы незначительные изменения в некоторых местах (например, контроль ограничений на память процесса), чтобы обеспечить возможность программисту определять свои сегменты.
сбор инструментов.
До того, как вы начнете писать драйвер SCSI для Linux, вам придется достать некоторые инструменты (ресурсы).
Самое важное - системный диск с системой Linux, желательно, жесткий диск с интерфейсом IDE, RLL или MFM. Во время разработки вашего SCSI придется много раз перестраивать ядро и перезапускать систему. Ошибки программирования могут привести к уничтожению информации на вашем диске SCSI, а также на посторонних носителях. Сохраняйте информацию на дисках!
Установленная система Linux может быть минимизирована: вы можете ограничиться библиотеками и утилитами компилятора GCC, текстовым редактором и текстом ядра. Также будут полезны дополнительные инструменты od, hexdump и less. Все эти программы свободно помещаются на диске размером 20 -30Мб.
Также вам потребуется подробная документация. Как минимум, вам нужно описание используемого вами адаптера. Так как Linux распространяется свободно, и так как вы тоже пожелаете поделиться с другими вашими разработками, существуют нагласные соглашения, согласно которым, если если вы хотите обнародовать вашу подпрограмму, к ней должен быть приложен об'ектный код; однако на данном этапе это не всегда случается.
Вам будет полезно описание стандарта SCSI. Описание жесткого диска обычно не требуется.
Прежде, чем начать, сохраните копии файлов hosts.h и scsi.h, а также одного из существующих драйверов ядра Linux. Это будет полезной рекомендацией во время написания.
Выделение и освобождение памяти: политика страничной организации
Когда любой процедуре ядра требуется память, она прекращает работу, вызывая get_free_page(). Это представляет собой более низкий уровень, чем kmalloc() (в действительности kmalloc() использует get_free_page(), когда возникает необходимость в расширении памяти).
get_free_mem() использует один параметр, приоритет. Допустимыми значениями являются GFP_BUFFER, GFP_KERNEL и GFP_ATOMIC. Она берет страницу из free_page_list, редактирует mem_map, обнуляет страницу и возвращает физический адрес страницы (заметте, что kmalloc() возвращает физический адрес. Логика mm зависит от идентичности отображения между логическими и физическими адресами).
Само по себе это достаточно просто. В действительности проблема состоит в том, что free_pagr_list может быть пуст. Если вы не испытывали потребности в использовании примитивных операций на этой стадии, вы попадаете в область вопросов изъятия стр аниц, которую мы будем тщательно рассматривать немного позже. Как крайнее средство (и это касается примитивных операций) страница изымалась из secondary_page_list (как вы могли догадаться, когда страницы освобождаются, в первую очередь происходит заполнение secondary_page_list).
В действительности манипуляции с page_lists и mem_map происходят в результате действий этого загадочного макроса, называемого REMOVE_FROM_MEM_QUEUE(), внутрь которого вы, возможно, никогда не захотите заглянуть. Достаточно сказать, что прерывания запрещены.
Теперь немного назад, к вопросу изъятия страниц. get_free_page() вызывает try_to_free_page, которая повторяет вызов shrink_buffers() и swap_out() в данной последовательности до тех пор, пока освобождение страниц не будет закончено успешно. Приоритет снижается при каждой успешной итерации, так что эти две процедуры чаще выполняют свои циклы по изъятию страниц.
Рассмотрим один проход swap_out():
Пройти по таблице процесса и получить ответ Q от замещаемой задачи. Найти таблицу страницы пользователя (не RESERVED) в области, принадлежащей Q. Для каждой page в таблице выполнить try_to_swap_out(page). Выход, когда страница освобождена.
Отметим, что swap_out() (вызываемая из try_to_free_page()) поддерживает статические переменные, таким образом можно получить ответ на вопрос где она была освобождена при предыдущем вызове.
try_to_swap_out() сканирует таблицы страниц всех пользовательских процессов и проводит следующую политику изъятия:
Не относиться легкомысленно к RESERVED страницам. Подвергать старению страницу, если она помечена как доступная (1 бит). Не трогать страницы, выделенные ранее (last_free_pages[]). Оставить грязные страницы с map_counts> 1. Снизить значение map_count для чистых страниц. Освободить чистые страницы, если для них не установлено соответствие. Заместить грязные страницы с map_count = 1.
Результатом действий 6 и 7 будет остановка процесса при текущем освобождении физической страницы. Действие 5 вызывает потерю одним из процессов чистой неразделяемой страницы, к которой ранее не было доступа (снижение Q->rss), что вовсе неплохо, но действия накопления за несколько циклов могут значительно замедлить процесс. В данном случае происходит 6 итераций, таким образом страница, разделяемая шестью процессами, может быть изъята, если она чистая.
Входы таблицы страниц изменяются и TLB становится недействительной.[Последнее вызывает интерес. В этом, кажется, нет необходимости т.к. доступные страницы не удаляются и многие таблицы страниц могут быть просмотрены между итерациями.... возможно, в результате прерывания, если такое произошло и имела место попытка урезать наиболее раннюю страницу?]
Текущая работа по освобождению страницы выполняется free_page(), являющегося дополнением get_free_page(). Она игнорирует RESERVED страницы, редактирует mem_map, затем освобождает страницу и изменяет page_lists, если для него не установлено соответствие. Для подкачки (см. п.6 выше) write_swap_pege() принимает вызов, но не делает ничего значительного с точки зрения дальнейшего управления памятью.
Рассмотрение деталей shrink_buffers() увело бы нас далеко в сторону. В первую очередь она следит за свободными буферами, затем списывает грязные буфера, далее занимается занятыми буферами и вызывает free_page(), когда у нее появляется возможность освободить все буфера на странице.
Заметим, что директории страниц и таблицы страниц наряду с RESERVED страниц не становятся изменяемыми, не изымаются и не стареют. Они отображаются в директории страниц процесса через зарезервированные таблицы страниц. Они освобождаются только пр и выходе из процесса.
Выделение памяти.
Выделение памяти в ядре отличается от выделения памяти на пользовательском уровне. Вместо функции malloc() выделяющее почти неограниченное количество памяти, существует kmalloc(), которая имеет некоторые отличия: Память выделяется кусками размером степени 2, за исключением кусков больше 128 байтов, размер коих равен степени 2 за вычетом части под метку о размере. Вы можете запросить произвольный размер, однако это будет неэффективно, так как 31 байтового об'екта, к примеру, выделяется 32 байтовый кусок. Общий предел выделяемой памяти 131056 байт. В качестве второго аргумента kmalloc() использует приоритет. Он используется в качестве аргумента функции get_free_page(), где он используется в качестве числа определяющего момент возврата. Обычно используемый приоритет - GFP_KERNEL. Если функция может быть вызвана с помощью прерывания используйте GFP_ATOMIC и приготовьтесь к тому, что функция может не работать. Это происходит из-за того, что при использовании GFP_KERNEL kmalloc() может не быть активным в любой момент времени, что не возможно при прерывании. Можно так же использовать опцию GFP_BUFFER, которая используется для выделения ядром области буфера. В драйверах устройств она не используется.
Для очистки памяти, выделенной kmalloc(), используйте функции kfree() и kfree_s(). Они также несколько отличаются от функции free() : kfree() - это макрос, вызывающий kfree_s() и работающая как free() вне ядра. Если вы знаете размеры об'екта, удаляемого из памяти, вы можете ускорить процесс, запуская сразу kfree_s(). У него существуют два аргумента : первым является аргумент kfree(), вторым - размер удаляемого об'екта.
См 2.6 для получения более подробной информации о kmalloc(), kfree() и о других полезных функциях.
Другой способ сохранить память - выделение ее во время инициализации. Ваша инициализационная функция foo_init() в качестве аргумента использует указатель на текущий конец памяти.Она может взять столько памяти, сколько хочет сохранить указатель/указатели на эту память и возвратить указатель на новый конец памяти.Преимуществом этого метода является то, что при выделении большого буфера в случае, если foo - драйвер не находит foo- устройства, подключенного к компьютеру, память не тратится. Функция инициализации подробно обсуждается в части 2.3.6. Будьте предельно аккуратны при использовании kmalloc(), используйте его только в случае крайней необходимости. Помните, что память в ядре не своппится. Аккуратно выделяйте ее, а затем каждый раз очищайте ее функцией frее().
! Существует возможность выделения виртуальной памяти с помощью vmalloc(), однако это будет описано лишь в главе VMM во время ее написания. В данный момент вам придется изучать это самостоятельно.!
Зачем нужны драйверы SCSI.
Ядро Linux содержит драйверы для следующих основных адаптеров SCSI: Adaptec 1542, adaptec 1740, Future Domain TMS-1660/TMS-1680, Segate ST-01/ST-02, Ultrastor 14F и Western Digital WD-7000.вы можете написать ваш собственный драйвер для неподдерживаемого адаптера. Также вы можете изменять готовые драйверы.