Как написать модуль ядра линукс

The Linux Kernel Module
Programming Guide

Copyright 2001, Peter Jay Salzman.

2004-05-16 ver 2.6.0

�������: ������ ����̣� (kis_an [at] linuxgazette [dot]
ru)

��������: Russian Linux Gazette

������������ ������ ���� ������������ �� ����� �������
The Linux Documentation Project.

������ ����� ���������������� �� �������� Open Software
License, version 1.1. ������ ����� �������� �� ������� ����� ��
������ http://opensource.org/licenses/osl.php
.

��� ����� ���������������� � ������� �� ��, ��� ��� �����
��� �������, �� ��� �����-���� ��������, � ��� ����� � ���
��������������� �������� �������� ������ ��� ����������� ���
������������� �����.

������ ������������ ������� ��������������� ���� ����� ���
��� �������������, ��� � ��� ������������� �����������, ���
������� ���������� ��������������� ���������� ������������
��������� ����, � ��� �� ��� �������, ��� ����������������
������ �������������� ������� Open Software License. �� ������
���������� � �������������� ��� ����� ��� ���������, ��� � �
����� ��������� �������. �� ������� �� ��������� ��������
������ ���������� ��� ��������������� ���� ����� �� �����
��������, ���� �� ������� ����� ��� �����������.

����������� ������ � �������� ����� ��������� ������
����������� �� �������� Open Software License, � ��������������
���������� �� ��������� ������ ������ �������� ����������. ����
�� �������� ����� �������� � ��� �����, �� ��� ������� �������
��� �������������. ���������� ��������� ������������ �������
(Peter Jay Salzman <p@dirac.org>) � ���������
���������� � �����������. �� ��������� ����������� �
��������� ������������������ ��������� ���������.

���� �� ���������� �������� � �������������� ��� ����� ��
������������ ������, �������������, ������������ ����������
�/��� �������� ����� ����� ������ ������� ������� � The Linux Documentation Project. �����
������� �� ������� ��������� ���������� ������������
����������� � LDP. ���� � ��� �������� ������� ���
�����������, ���������� ������ ������������ ������� ��
������, ���������� ����.


�������� ��������
2-1. hello-1.c
2-2. Makefile ���
������ ����
2-3. hello-2.c
2-4. Makefile ���
������ ����� �������
2-5. hello-3.c
2-6. hello-4.c
2-7. hello-5.c
2-8. start.c
2-9. stop.c
2-10. Makefile ���
������ ���� �������
4-1. chardev.c
4-2. Makefile
5-1. procfs.c
5-2. Makefile
5-3. procfs.c
6-1. chardev.c
6-2. chardev.h
6-3. ioctl.c
6-4. Makefile
6-5. build.sh
7-1. syscall.c
7-2. «�������» �� ����
(export_sys_call_table_patch_for_linux_2.6.x)
7-3. Makefile
7-4. README.txt
8-1. sleep.c
9-1. print_string.c
9-2. kbleds.c
10-1. sched.c
11-1. intrpt.c

�����������

1. �� �������

��� ����� ���������� ���� �������� ��� ���������� (Ori
Pomerantz) ��� ���� Linux ������ 2.2. � ��������� � ��� ��
������� ������� �� ����������� ������ ��� ���� ������. � �����
������ ���� Linux ���������� ������ �����������. ����� ��������
(Peter Jay Salzman) ���� �� ���� ���� �� ��������� ���������
��� ���� ������ 2.4. � ��������� � ����� �� ����� ����������
���������� �������, ����� ���������� ������ ��� ������ �
��������� �� �����������, ����������� ���� ������ 2.6. �����
�������, ����� ������ (Michael Burian) ������ �� ���������
��������� ����� ��� ���� 2.6.


���� Linux — ����������� ������������� ������. � �����
�������� ����� ������ ����� ����� ������ — ������� ��
���������� �������� �� ����� ��� ��������� ��, ��� ������������
��������. ����� � � (����� ��������) ������ ������� ���������
����� ��������� ��� ������ ���������� ����� ����. �����
�������, LKMPG (Linux Kernel Module Programming Guide), �������
������ 2.4.x ��������� � ���� 2.4.x, � LKMPG 2.6.x — � ����
������ 2.6.x. �� ������ �� ��������� ���������� �������� �
����� ������� ���������. �������� �������� ��� ����������
������ ���������� � ��������������� ������� ���������.

�������� ��� � ���������� �������� �� ��������� � ����� ����
���������� ���������� �����������, �� � ������ �� ����
�������������. ���� ������ ���������� — «��������� ����������» (�����
12), ��� ���� ����������� �������� ��������� � �����������
x86.


3. �������������

��� �������� �������� ���� ������������� Yoav Weiss, ��
�������������� �����������, ���������� ��������� � ��������
�����������. �� ��� �� ����� �� ������������� ����� ���������
�� �����������, ������� ������ �� ����� ��������, �������
�������� �� ������ � ��������� ��������� �� �������,
������.

����� �������� ���� ������������� ���, �� �� ��� ��������
��� ������� ������� � ������� LKMPG. � ��� �� ������
����������, ����� ������� ����� (���� ����� ������� ��������) �
����� ���� �� ����� � �������������. �� ��� �� ����� ��
������������� ������ �������, ������� ������ ���������� ������
� �������� ��������� �� ������� LaTeX � ������ docbook. ���
���� ������� � ������ ������, �� �� ���������� ����
�������.

��������� ������� ���� ���������� ������� www.kernelnewbies.org, � �������� �����
��������� � ����� ������, �������, � ������, ����� �� �����
����� ����� ���������� �������, ������ «��������»
�� kernelnewbies.org � ������� ��������. ���� ���
����������� ������ ��� ���� ����, ������ — � ���� ���� ����
� �� «����»!

� ��� � � ������ �� ������� ����� ������������� � �����
������� �. ��������� � ������ ���������� �� ������ �� ���
������������ ������������ �������, �� � �� ��, ��� ���������
����������� �� �������� ���, ����� ����������� � ���, ��� ���
��������.

������� �������� ������������� ���, ��� ���� ����
����������� � �������� ��� ���� ���� ����������� �� ���������.
��� ������� ������, ����� ������, ������ ����� ��������� � ����
�����.


����� 1. ��������.


1.1. ���
����� «������ ����»?

����, �� ������ ������ ������ ����. �� ������� � ������ C �
� ��� ���� ���� �������� ������� ��������, � ������ �� ������
��������� ����, ��� ���������� ������� ��������. ����, ��� ����
��������� ��������� ����� «������» �������� �������
��� «���������» ���������.

��� ��� �� ����� «������ ����»? ������ — ���
����� ���, ������� ����� ���� �������� ��� �������� ����� ��
���� �������������. ������ ��������� �������������� �����������
���� ��� ������������� ������������ �������. ��������, ���� ��
�������������� ������� ����, �������� ���������, ��������� ����
����������������� � ����������� ����������. ��� ����������
��������� ������� ��� �������� �� ������ ���������� ���� �
��������� ����� ����������� ����� � ����. ��� ����, �����
���������� � ���� ����� ������������, �������� �� �������������
�������.


1.2. ��� ������ �������� �
����?

�� ������ ����������� ������ ����������� ������� ��������
lsmod, ������� � ���� �������
���������� �� ������������ ���������� � ����� /proc/modules.

��� �� ������ ����������� �����? ����� ���� ������������
������������� � ��� ��� ���� �������������� ������������, ���
�� ����������� � ������, �� ����� kmod [1]
�������� ������� modprobe,
��������� �� ������ � ����:

  • �������� ������, �������� softdog ��� ppp.

  • ������������� �������������, �������� char-major-10-30.

���� ������� modprobe
���������� ������������� �������������, �� ���
������� �������� �������� ��� ���������������� ������ � �����
/etc/modules.conf, ��� �������
�������������� �������������� ���������� � ������������ ���
������, ��������:

alias char-major-10-30 softdog    
    

��� ������������� �����������: «������� ��������������
�������������� ������������� ���� ������ softdog.ko».

����� modprobe ����������
���� /lib/modules/version/modules.dep, �����
��������� — �� ����� �� ��������� ��� �����-���� ������, ��
������� ����� �������� �������� ������. ���� ���� ���������
�������� depmod -a � ���������
����������� �������. ��������, ������ msdos.ko �������, ����� �������������� ���
�������� ������ fat.ko. ���� ������
������������ ��� ���� (�����
�������, ���������� � �.�.), ������� ������������ �������
, �� �������, ���
«������ ������� ��
������ «.

� ������� modprobe ��������
insmod, ����� �������
��������� �����������, ��� �������������� ������������, ������,
� ����� � ����������� ������. ������� insmod, ������� modprobe ��������� �� �������, /lib/modules/version/ [2]
����������� ���� � ������� ����. ������� insmod ������ �� «�����» �
���������� ������� ����, ���� ��� «�����» �������
modprobe. ����� �������,
���� ��� ���������� ��������� ������ msdos, �� ��� ���������� ���� ���������
�������:

insmod /lib/modules/2.6.0/kernel/fs/fat/fat.ko
insmod /lib/modules/2.6.0/kernel/fs/msdos/msdos.ko
  

��� ������:

modprobe -a msdos   
  

� ����������� ������������� Linux, ������� modprobe, insmod, depmod ������ � ������
������ modutils ���
mod-utils.

������ ��� ��������� ��� �����, � ��������� �������
������������ � ���������� ����� /etc/modules.conf:

### This file is automatically generated by modules-update
#
# Please do not edit this file directly. If you want to change or add
# anything please take a look at the files in /etc/modules.d and read
# the manpage for modules-update.
#
### modules-update: start processing /etc/modules.d/aliases
# Aliases to tell insmod/modprobe which modules to use 
path[misc]=/lib/modules/2.6.?/local
keep
path[net]=~p/mymodules
options mydriver irq=10
alias eth0 eepro
  

������, ������������ � ������� «#» ��������
�������������. ������ ������ ������������.

������ path[misc] ��������
modprobe � ���, ��� ������
���� �� ��������� misc ������� ������ � �������� /lib/modules/2.6.?/local. ��� ������, �����
������ ��������� ��������� �������.

������ path[net] ������ �������
���������� ������� ��������� net, ������, ���������
keep, ������� ����, ��������,
��� ������� ~p/mymodules �� ��������
����������� ���� ������ ������� (��� ��� ���������� � ������ �
path[misc]), � ���� ����������� �
����.

������ alias ������� � ���, ���
���� ��������� �������� ������ �� �������������� ��������������
eth0, �� ������� ��������� ������
eepro.ko

�� ���� �� ��������� � ���� ����� ������, ��������:

alias block-major-2 floppy  
  

��������� modprobe ���
����� � ������������� ����������� ��������� ���������, �������
������������ � ����������� ������.


1.2.1.
������, ��� ����������

������, ��� �� ��������� � ����������������, ����������
�������� ��� ��� ��������. ����� ������� ����� ����
������������� ����� � ������ �� ��� ����� ������ �����
������. ��������, �������������� � ��������� ���� ������
��������� «Hello World!» ��� ������ ����� ���������
�������� ������� �������. ������, ����� ����������� �����
���������� �����������, ������, ��� �������, ������������ ���
������ �������.


1.2.1.1.
�������� �������� ������

������, ���������������� � ����� �����, ����� ��
����������� ������ �����, ���� � ���� ������� ��������
�������� ������ �������. � ����������� ������������� ����
���������� � ����� ����������. �� �� ���������� ���������
�������� �������� ������ � ���� �����, ������� ��� ��������
���������������, � ������ ������������� �������,
����������� ���� ��� ��������� ��������� ��������
������.


1.2.1.2. ������ �
XWindow

�� ������������ ����������� ������� � ���������� ���
�������, ����������� � �����. ����� ����, �� ���������� ��
���, ����� ��� ������, ��������� � ��������������� ��������
�������, ����������� � �������� �������, �� ��������� ��
��������� �������. �������� ������ �����, XWindow ��
�������� ��� ���������� �������� �����.

������ �� ����� ������������ ������� printf() ��� ������ �� �����, �� ���
����� �������������� ��������� �� �������, ������� �
�������� ����� �������� �� �����, �� ������ � ���������
�������. ���� �� ������ ����������� �� ���� ���������,
�������� xterm, �� ��� ���������
����� �������� ������ � ��������� ������ � �� �����
���������� �� �����. ����� ������ ��������� ��������� ��
������, ��������� � ��������� ������� (��
�����������:
��� ����������� �������� �� ����� ���
�� ������� ������� �� ������ ��������� �� �����, ��� ���
����� ���� ��������� � ��������� �������, � ���� ������ ���
��� ���� /var/log/kern.log
).


1.2.1.3. ��������
����������

��������, ��������������������� �������������� ��������
������ ����, �� ������� ��� �������� ������ �������������
�������. ��� ����� �������� ������������ ��������.

�� ����� ������ ������ — �������� ����� ������������
������ ����. ��� ������ ����� ������� ��� �����������
������ ������������ ����� ���� Linux. � ����� ����� ������:
«������������� ����� ��� ��� �� �����, � ������� ��
������ ����� ����������».

����� �������� ���� ���� �������, �� ����������� �������
� ���������� �������� ������ ����. ������� �������� ������
���� �� ������� �� ����� �� ������, ���������������� ����
Linux. �� ����� ��������� ����������� ����������� �
«Kernel HOWTO».

��� ��� �� ��������� ��������, �� �������� ����� �������
� � �����������. ��-��������� gcc
����� ������ ������������ ����� ���� ������ �� ���, ���� ��
�� ���������� (��� ������� ��� ������� /usr/src/. ��� �������� �����
�������������� �������� ����� ���������� -I.


����� 2. Hello World

2.1. «Hello,
World» (����� 1): ���������� ������ ����.

����� ������ �������� ����������� ������� ���� ������
��������� ��� ��������� ����������, ��� ���� ���������, �������
�������� «Hello, World!» ������� �����������
��������. ������������� �������� �� ���������������� ����������
� ��������� «Salut, Mundi». � �� ���� — ���
��������� � ������, ������� �������� � ���� ���������, ��
�����, ��� ����� ����� � �� �����. �� ������ � ����� ��������
«Hello world», �� ������� �������� ��������� �������
������� �������� ������� ����.

���� ���������� �������� ����� ������ �������� ������ ����,
����� ������ ��������. ���� ������ ������������ � ���
����������, � ���������� � ������ ������ �� ������� � ���������
�������.

������ 2-1. hello-1.c

/*  
 *  hello-1.c - ���������� ������ ����.
 */
#include <linux/module.h>       /* ��������� ��� ������ ������ ���� */
#include <linux/kernel.h>       /* ����� ��������� ����������� KERN_ALERT */

int init_module(void)
{
        printk("<1>Hello world 1.n");

  /* 
   * ���� ������� ��������� ��������, �� ��� ����� ���������� ��� ������� ������,
   * ��������� � �������� ������ init_module; � ���������� ������ �� ����� ��������. 
   */
        return 0;
}

void cleanup_module(void)
{
        printk(KERN_ALERT "Goodbye world 1.n");
}

����� ������ ���� ������ ����� �� ������� ���� ���� �� ���
�������: ������� ������������� ������ — init_module(), ������� �������� insmod �� ����� �������� ������, � �������
���������� ������ ������ — cleanup_module(), ������� ��������
rmmod. ������� � ����, ������
2.3.13, ���������� � ���������� ��������� � �������� �������
���� �����. ������ �� ������ ������ �� ���� �����. ��� ���
������� ����� ������� � ������� Hello World (����� 2). ����� �����
���������� �������� ����� ����������������, ������ ������
��-�������� ���������� ������������ ����� init_module() � cleanup_module().

������ ������� init_module()
��������� ����������� ����������� ������-���� ������� ���
�������� �����-���� ������� � ���� ����� ����� (�������, ���
�������, �������� ����� ������������� ��������, ��������
������������ ������ ������� � ����). ������� cleanup_module() �������� ������
������������������, ��� ���������� «�����» ���������,
�������� �������� init_module(), ���
������ �������� ������ ����������.

� �������, ����� ������ ���� ������ ���������� ������������
���� linux/module.h. � ����� �������
�� ���������� ��� ���� ���� — linux/kernel.h, �� ���� ��� ����, �����
�������� ������ � ����������� KERN_ALERT, ������� ����� �������� �����
����������� � ���������
�������


2.1.1.
���������� � printk()

�������� �� ����� ������������� ��������, �������
printk() ����� �� ������������� ���
������ ���������� �� �����, ���� �� ������ �� ��, ��� ��
������������ �� � ����� ������� ������ ��� ���� ����!
�������� ���������� ���� ������� — ���� ���� ��������
����������� ������� � ��������������. �������, ������ �����
printk() �������������� ���������
����������, � ����� ������� ��� <1> � KERN_ALERT. ����� � ���� ���������� 8
��������� ������� ���������� ��� ������� printk() � ������ �� ��� ����� ����
����������������, ����� ������� ��� ������������� ������
�����, �������� ������ (����� ������� ���������� � ��
�������� �������� �� ������� � ����� linux/kernel.h). ���� ������� ���������� ��
�����������, �� ��-��������� �� ����������� ������ DEFAULT_MESSAGE_LOGLEVEL.

������� ����� � ����������� ���������� ����� �����. �����
�� ������� ������� �������� �������� ������� �� �������. ��
�������� ��������� ������ ����� �������� ������� ����������
�������� ���������, �������� ���: <4>. ��� ���� ����� ����� ������������
������� ����������������, ��������: KERN_WARNING.

���� ����� ������� ����, ��� int
console_loglevel, �� ��������� ��������� �� �����. ����
�������� � syslog, �
klogd, �� ��������� �������
����� � � ��������� ������ /var/log/messages, ��� ���� ��� ����� ����
�������� �� �����, � ����� � �� ����������. �� ������������
���������� ������� ������� ���������� KERN_ALERT ��� ����, ����� �������������
����� ��������� �� ����� �������� printk(). ����� �� �������� ���������
��������� ������� ����, �� ������ ������������ ������
���������� �������� ���������� ��� ���������� ��������.


2.2.
������ ������� ����

����� ������ ��� ��������������, ��� ���������� ����������
�������� gcc ��� �����. �����
����, ���������� ����� ������ ��������������� � ��������������
������������� ���������. ������ ������ ���� ���������
����������, � ���� �������, �� ������������ � ��� �����������
���� ��������� ��������� ����������� � Makefile-��. �������� �� �������������
�����������, � Makefile-��, ��
��������� �������, ������������� ����� �������� ����������
���������� ���������, ��� ���������� � ������������� ����
�������� ����� �������� ���������� �����. � ������� ��������
kbuild, � ���������� �������
������ ������� ����������� ������� ������ ���������
������������ � �������� ������ ����. �������������� �������� ��
������ �������, ������� �� �������� ������ ������������ ����
(��� � ����� ������), �� ������� � ����� linux/Documentation/kbuild/modules.txt.

� ������ ��������� ������� ��� � ���� ������ hello-1.c .
��������������� Makefile ��������
����� ���� ������:

������ 2-2. Makefile ��� ������ ����

obj-m += hello-1.o

��� ����, ����� ��������� ������� ������ ������, �����
������� make -C /usr/src/linux-`uname
-r` SUBDIRS=$PWD modules
(�� �����������: ���� � ��� �
�������� /usr/src ������������
������������� ������ linux �� �������
� ��������� �������� ����, �� ������� ������ ����� ����
��������� ��������: make -C
/usr/src/linux SUBDIRS=$PWD modules
). ��
����� ������ ���� �������� ����� ��������:

[root@pcsenonsrv test_module]# make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules
make: Entering directory `/usr/src/linux-2.6.x
  CC [M]  /root/test_module/hello-1.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test_module/hello-1.mod.o
  LD [M]  /root/test_module/hello-1.ko
make: Leaving directory `/usr/src/linux-2.6.x    
    

�������� ��������: � ����� ������ 2.6 ������� �����
���������� �� ���������� ��������� ������ �������. ������, ���
����� ���������� .ko (������ ��������
.o), ��� �������� �� �� �������
��������� ������. �������������� ���������� �� ����������
Makefile-�� ������� �� ������� �
linux/Documentation/kbuild/makefiles.txt.
����������� �������� ���� �������� ������, ��� �������
����������� � �������� Makefile-��.

����, ������ ������������� ������ — ������ ����� ���������
�������������� ������! ����� ������� insmod ./hello-1.ko (������������ ���������
� «�����������» ���� �� ������ ������ ������
������������, ������ �� ������� ��� ��������).

����� ����������� ������ ���� ��������� � ������ /proc/modules, ��� ��� ������ ���� ���� �
������� ���������� ����� �����. ��� �� ������ ���������, ���
������ ���� ������ ����. � ��� ��� � �����������, ������ ��
����� ����� �� ������� ���� ����! ������� ������������
��������� �������, ��������� ������ �������� rmmod hello-1 � ��������� � ���� /var/log/messages, ����� �� ������� ���������,
������� ������������ ��� ������. (�� �����������: � ���� ������
�� ����� ������� ��������� �� ����������, ���� ��� ��������� �
����� /var/log/kern.log
).

� ������ ��������� ����������: �������� ���������� �����
hello-1.c ���, ����� �������
init_module() ���������� �� �����
���� ��������� �������� � ��������� — ��� ���������?


2.3. Hello
World (����� 2)

��� �� ��� ���������, ������� � ����, ������ 2.3.13,
���������� � ���������� ��������� � �������� ������� ������
���� �����. ����������� ��� � ������� ����������������
module_init() � module_exit(). ��� ���������� � �����
linux/init.h. ������������ ���������:
��������� � �������� ������� ������ ���� ���������� ���� �����,
� ������� ���������� ��� �������, � ��������� ������ ��
�������� ������ ������� ����������. ���� ���������� ������
������������� ���� ����������������:

������ 2-3. hello-2.c

/*  
 *  hello-2.c - ������������ ������������� ���������������� module_init() � module_exit().
 */
#include <linux/module.h>       /* ��������� ��� ������ ������ ���� */
#include <linux/kernel.h>       /* ����� ��������� ����������� KERN_ALERT */
#include <linux/init.h>         /* ����� ��������� ����������� �������� */

static int __init hello_2_init(void)
{
        printk(KERN_ALERT "Hello, world 2n");
        return 0;
}

static void __exit hello_2_exit(void)
{
        printk(KERN_ALERT "Goodbye, world 2n");
}

module_init(hello_2_init);
module_exit(hello_2_exit);

������ �� ����� � ����� ������ ��� ��������� ������ ����.
�������� ������ ������� ������ ����� ������:

������ 2-4. Makefile ��� ������ �����
�������

obj-m += hello-1.o
obj-m += hello-2.o

������ ��������� � ���� linux/drivers/char/Makefile. �� �����
��������������� ��� ������ ������������ Makefile ������ ����. ����� �����, ��� ���
������� ������ «������» � ���� (obj-y), �� ����� ���
������ obj-m. ������? �������� � ������ ��������� ���������
�������� ����� ������ �����. ��� ������ ���� obj-$(CONFIG_FOO)
����� �������� �� obj-y ��� obj-m, � ����������� �� ��������
���������� CONFIG_FOO. ��� ���������� �� ������� ����� � �����
.config, ������� ��� ������ �� �����
���������������� ���� � ������� make
menuconfig
��� ���-�� ����� �����.


2.4. Hello World
(����� 3): ���������������� __init � __exit

��� ������������ ������������ ����, ������������, ������� �
������ 2.2. �������� �������� �� ��, ��� ���������� �����������
������� ������������� � ���������� ������ ������.
���������������� __init ���������
����, ����� ���������� ������������� ������, ���������� ������,
���������� ��������, ������ ��������� ��� ������ � ����������
������� � �� ����� �������� ������� ��� ����������� �������.
���� �� �������� ����������� ���� ���� ������� �������������
����������� ������, �� ��� ������� �� ���� �����.

�� �� ��������� � � ������� __initdata, �� ������ ��� ����������.

���������������� __exit ���������
���� ���������� ������, ���������� ��������, �� ������ ���
���������� �������, �� ����������� ������ ��� ����������������
�� ��������� �������. ����� ��, ���� �� ����������� ���� —
����� ���������� ������� ���������� ������ ������, �� ������
�������, ��� ��� ���������� ������� ��� �� �����, � �� �����
��� ��� ����������� ������� — ������ ����������.

��� ���� ������� ���������� � ����� linux/init.h � �������� �� ������������
�������������� ������ � ����. ��� ��������� ����������� ������
�� ������, �� ����� ��������, ��������� �������� ������
����������: Freeing unused kernel
memory: 236k freed. ��� ��� ��� � ���� ��������� ������
������ ����������������.

������ 2-5. hello-3.c

/*  
 *  hello-3.c - ������������� ���������������� __init, __initdata � __exit.
 */
#include <linux/module.h>       /* ��������� ��� ������ ������ ���� */
#include <linux/kernel.h>       /* ����� ��������� ����������� KERN_ALERT */
#include <linux/init.h>         /* ����� ��������� ����������� �������� */

static int hello3_data __initdata = 3;

static int __init hello_3_init(void)
{
        printk(KERN_ALERT "Hello, world %dn", hello3_data);
        return 0;
}

static void __exit hello_3_exit(void)
{
        printk(KERN_ALERT "Goodbye, world 3n");
}

module_init(hello_3_init);
module_exit(hello_3_exit);


2.5. Hello
World (����� 4): ������� �������������� � ����������������
�������

���� � ��� ����������� ���� 2.4 ��� ����� �������, ��
���������, �� ����� ������� �������� �������, ��� ��������
����������� � ����������� ����:

# insmod hello-3.o
Warning: loading hello-3.o will taint the kernel: no license
  See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Hello, world 3
Module hello-3 loaded, with warnings
    

� ���� ������ 2.4 � ���� ��� �������� �������� ��������
��������, ����� ����� ����������� ������������� ������������ ��
������������� �������������� (�� ����������) ����. ������
������� �������������� ������ ����� � ������� ����������������
MODULE_LICENSE(). ���� ����������
�������� �� ����� linux/module.h
(�� �����������: �
���� �� ���� �������� ��������� ����� ������������ �� �������
����
):

/*
 * � ��������� �����, ��� ����������� ��������� ��������, ������� ���������
 * ��������������
 *
 *      "GPL"                           [GNU Public License v2 ��� ����]
 *      "GPL v2"                        [GNU Public License v2]
 *      "GPL and additional rights"     [GNU Public License v2 � ��������������� �������]
 *      "Dual BSD/GPL"                  [GNU Public License v2
 *                                       ��� BSD license]
 *      "Dual MPL/GPL"                  [GNU Public License v2
 *                                       ��� Mozilla license]
 *
 * ����� ����, ������������� ������� ��������� ��������������
 *
 *      "Proprietary"                   [�������������, �� ��������� �������]
 *
 * ����� ������������ ����������, ��������������� ������� ��������������, 
 * ������, �� ��������� � Linux ��� ����������� �������� GPL, ��� �������� 
 * ��������, ��� ��� ��� �� �������� ���������. 
 * ������� ����, ��� LGPL ������� � GPL
 *
 * �� ��� ���� ��������� ������
 * 1. modinfo ����� �������� �������� � �������������� ��� ��� �������������, 
 *    ������� ������, ����� �� ����� ����������� ��������� ��� ���������
 * 2. ���������� ����� ������������ ������ �� ������� (bug reports), ����������� 
 *    � ������������� �������
 * 3. ���������� ����������� ��������� ����� ��������� ����������� �������, 
 *    ����������� �� ����� ����������� ��������
 */   
    

����� ��� ��, ��� �������� ������ ����� ��������������
������ MODULE_DESCRIPTION(), ���
������������ ��������� — MODULE_AUTHOR(), � ��� �������� �����
���������, �������������� ������� — MODULE_SUPPORTED_DEVICE().

��� ��� ���������������� ������� � ����� linux/module.h. ��� �� ������������ ����� �
������ ���� ��� �������� ������, ������� ����� ���� �����������
� ������� objdump. ����������
� ������� ������� grep
����������, ��� ������ ������� ���������� ��� ������� (�
�������� linux/drivers).

������ 2-6. hello-4.c

/*  
 *  hello-4.c - ������������ �������� ������.
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#define DRIVER_AUTHOR "Peter Jay Salzman <p@dirac.org>"
#define DRIVER_DESC   "A sample driver"

static int __init init_hello_4(void)
{
        printk(KERN_ALERT "Hello, world 4n");
        return 0;
}

static void __exit cleanup_hello_4(void)
{
        printk(KERN_ALERT "Goodbye, world 4n");
}

module_init(init_hello_4);
module_exit(cleanup_hello_4);

/*  
 *  �� ������ ���������� � ������� ������, ��� ��� �������� ����:
 */

/* 
 * ��������� ����� �������������� � "�����������" ����, ������� ��� ��� GPL. 
 */
MODULE_LICENSE("GPL");

/*
 * ��� �����������:
 */
MODULE_AUTHOR(DRIVER_AUTHOR);    /* ����� ������ */
MODULE_DESCRIPTION(DRIVER_DESC); /* ���������� ������ */

/*  
 *  ���� ������ ���������� ���������� /dev/testdevice.  � ������� ������� ����
 *  ������ MODULE_SUPPORTED_DEVICE ����� ���� �����������
 *  ��� �������������� ��������� ������, �� ����
 *  �� ������ ������������� � ������������ �����.
 */
MODULE_SUPPORTED_DEVICE("testdevice");


2.6. �������� ������
���������� ��������� ������

������� ����������� �������� ������ ��������������
���������� ��������� ������, �� �������� ��� �� � �������
argc/argv.

��� ������ ��� ����� �������� ���������� ����������, �
������� ����� �������� ������� ���������, � ����� ��������
������ MODULE_PARAM(), ��� �������
��������� ������ ������� ����������. �������� ���������� �����
���� �������� ������ � ������� ������ insmod ��� modprobe.
��������: insmod mymodule.ko
myvariable=5
. ��� ������� �������, ����������
���������� � ������ ���������������� ������� ��������� � ������
������. ������ ���� �������� ���, �� ������ ���������, ��������
��������� ����������.

������ MODULE_PARAM() ��������� 2
���������: ��� ���������� � �� ���. �������������� ���������
���� ����������

  • «b» — byte (����);

  • «h» — short int (�������� �����);

  • «i» — integer (�����, ��� �� ������, ��� �
    ��� �����);

  • «l» — long int (������� �����, ��� �� ������,
    ��� � ��� �����);

  • «s» — string (������, ������ ����������� ���
    char*).

��� ���������� ����

char *

,
insmod ����� ���� ��������
����������� ������. �� ������ ������ ����������������
���������� ���������� ��-���������, �� ��������� — ��� ���
����, ����� ����� ������ ��� ����������������. ��������:

int myint = 3;
char *mystr;

MODULE_PARAM(myint, "i");
MODULE_PARAM(mystr, "s");    
    

���������-������� ��� �� ���������. ����� �����,
�������������� ������� ���� ���������, ���������� ������������
������ �������. ��� �����, ����������� ������� — ����������� �
������������ ���������� ��������. ��������, ������ �����,
������� ������ ����� �� ����� 2-� � �� ����� 4-� ��������,
����� ���� �������� ���:

int myintArray[4];
MODULE_PARAM(myintArray, "2-4i");
    

����������, ����� ��� ������� ��������� ������ �����
�������� ��-���������, �������� ������ ������ �����-������.
������ ����� ��������� �������� ���������� �� ��������
��-��������� � ���� ����� �������� ���� �������������
���������, �� ���������� � ��������������� ����������������
(������ ������������� ����� ����������� ����).

�, �������, ��� ���� ���������������� — MODULE_PARAM_DESC(). ��� ������������ ���
�������� ������� ���������� ������. ��������� ��� ���������:
��� ���������� � ������ ��������, � ��������� �����.

������ 2-7. hello-5.c

/*
 *  hello-5.c - ������ �������� ������ ���������� ��������� ������.
 */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter Jay Salzman");

static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";

/* 
 * module_param(foo, int, 0000)
 * ������ �������� -- ��� ����������,
 * ������ -- ���,
 * ��������� -- ���� ���� �������
 * ��� ����, ����� ��������� � sysfs (���� ��������� ��������) �� ����� ������� ������.
 */

module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, "A short integer");
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "An integer");
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, "A long integer");
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, "A character string");

static int __init hello_5_init(void)
{
        printk(KERN_ALERT "Hello, world 5n=============n");
        printk(KERN_ALERT "myshort is a short integer: %hdn", myshort);
        printk(KERN_ALERT "myint is an integer: %dn", myint);
        printk(KERN_ALERT "mylong is a long integer: %ldn", mylong);
        printk(KERN_ALERT "mystring is a string: %sn", mystring);
        return 0;
}

static void __exit hello_5_exit(void)
{
        printk(KERN_ALERT "Goodbye, world 5n");
}

module_init(hello_5_init);
module_exit(hello_5_exit);

������� �������� ������������������ � ���� �������:

satan# insmod hello-5.o mystring="bebop" myshort=255
myshort is a short integer: 255
myint is an integer: 420
mylong is a long integer: 9999
mystring is a string: bebop

satan# rmmod hello-5
Goodbye, world 5

satan# insmod hello-5.o mystring="supercalifragilisticexpialidocious" myint=100
myshort is a short integer: 1
myint is an integer: 100
mylong is a long integer: 9999
mystring is a string: supercalifragilisticexpialidocious

satan# rmmod hello-5
Goodbye, world 5

satan# insmod hello-5.o mylong=hello
hello-5.o: `hello' invalid for parameter mylong
    


2.7. ������, ��������� ��
���������� ������

������ ��������� ������������� ���������� �������� ������
������ � ���������� ������. � ���� ������ kbuild ����� ������� �� ���� ���
«�������» ������, � Makefile ������� ��������� ���� ���� �������, �
������ �������! ���� ���������� ������ ������, ���������� ��
���� ������:

������ 2-8. start.c

/*
 *  start.c - ������ ������, �������� ����� �������� �������� � ���������� ������
 */

#include <linux/kernel.h>       /* ���-���� �� ����� ��� ����! */
#include <linux/module.h>       /* ��������� ��� ������ ������ ���� */

int init_module(void)
{
        printk("Hello, world - this is the kernel speakingn");
        return 0;
}

������ 2-9. stop.c

/*
 *  stop.c - ������ ������, �������� ����� �������� �������� � ���������� ������
 */

#include <linux/kernel.h>       /* ���-���� �� ����� ��� ����! */
#include <linux/module.h>       /* ��������� ��� ������ ������ ���� */

void cleanup_module()
{
        printk("<1>Short is the life of a kernel modulen");
}

������ 2-10. Makefile ��� ������ ����
�������

obj-m += hello-1.o
obj-m += hello-2.o
obj-m += hello-3.o
obj-m += hello-4.o
obj-m += hello-5.o
obj-m += startstop.o
startstop-objs := start.o stop.o


2.8. ������ �������
��� ������������ ����

�� ��� ������������� ��� ����������� ���� ����, �������
��������� �������� ��� ������� �����, �������� �����, ���
(MODULE_FORCE_UNLOAD) — ����� ��� ����� ��������, �� �� ������
����������� �������������� �������� ������ (����������� �������
rmmod -f module_name), ����
���� ���� «�������» ���� �������� �������������. ���
����� ������� ��� ���������� ����� �� ������������� �������, �
�������� ������� ������.

��� �� �� �� ����, �� �������� ����� ��������� ���, ��� ���
����������� ��������� ������ � ����� ����������������� ����,
��������, �� ������ �������, ��� � ������, ����� �� �� ������
����������� ���� �� ����� ���� ������������. ���� �� ��
������������� ������������� ����� ��������, �� ������ ������
���������� ��� ����� �����.

���� �� ���� ���������� ������ � ��������� �������� ���� �
������������ �� ��� ������ ������ ������, �� � �����������
�������, ��� ������� ��������� ��� � ���������� ����, ��
�������� ��������� ��������� �� ������:

insmod: error inserting 'your_module_name.ko': -1 Invalid module format  
    

����� ��������� ���������� ����� �������� � ���� /var/log/messages:

Jun  4 22:07:54 localhost kernel: your_module_name: version magic '2.6.5-1.358custom 686 
REGPARM 4KSTACKS gcc-3.3' should be '2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3'    
    

������� ������� — ���� ���� ������������
«���������» ��� ������ ��-�� �������������� ������
(������ — ��-�� �������������� �������� ������). ���������
������ ����������� � ��������� ����� � ���� ����������� ������,
������������ �� ����� vermagic:. ��� ������ ����������� ��
����� ���������� ������ � ������ init/vermagic.o. ����������� ��������� ������
(��� �� ��� � ��������� �������������� ��������) �����
����������� ������� modinfo
module.ko
:

[root@pcsenonsrv 02-HelloWorld]# modinfo hello-4.ko 
license:        GPL
author:         Peter Jay Salzman <p@dirac.org>
description:    A sample driver
vermagic:       2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3
depends:            
    

��� ����������� ����� ����������� ����� ���������������
������ —force-vermagic
(������� modprobe,
����.
�����.
), �� ��� ������� ������������ ������ �
���������� ����������� ��� ��������������� ������� �������.
�������������, ��� �������� ����������� ������ � ���������
���������� ����, � ������� ���� ������� ������� ����. ������:
«��� ��� �������?» � �������� ����� ��� �����������
���������� � ������ �����.

������ ����� ��� ���������� ���������� ������ � ���������
�������� ���� ��� �� ������, ��� � ������� ����. ������� ����
������������ �������� ����, ��� ������� �� ������������� �
�������� /boot, ��� ������, ���-��
����� config-2.6.x. ������ ����������
��� � ������� � ��������� �������� ���� �� ����� ������.

�������� � ��������� �� ������, ������� ���� ��������� ����,
� ��� ��� ����������� ��������� ���. ��� ������, ������ ����
����������� �� �� �����, �� ���� ���������� ������� �������,
����� ���� ���������� ��������� ������. ��� ��������
����������� ���� � ������� ����� custom � ��������� ������ ������. ������
�������� Makefile ���� �
��������������, ��� ���������� � ������ � ��������
������������� �������� ����. ��������, � ������ ����������
������ Makefile ������ ���������
������

VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 5
EXTRAVERSION = -1.358custom
...    
    

������ ��������� make,
����� �������� ���������� � ������:

[root@pcsenonsrv linux-2.6.x]# make
CHK     include/linux/version.h
UPD     include/linux/version.h
SYMLINK include/asm -> include/asm-i386
SPLIT   include/linux/autoconf.h -> include/config/*
HOSTCC  scripts/basic/fixdep
HOSTCC  scripts/basic/split-include
HOSTCC  scripts/basic/docproc
HOSTCC  scripts/conmakehash
HOSTCC  scripts/kallsyms
CC      scripts/empty.o
...    
    

���� �� �� ������� ��������� ������������ ����, �� ������
�������� ������� ������ (CTRL_C) ����� �� ����� ���������
������, ������������ �� ����� SPLIT, ��������� � ���� ������ ���
����������� ����� ��� ����� ������. �������� � ������� �
��������� �������� ������ � ������������ ���. ������ ���������
������ ������ ����� � �������� ��������������� ������ ��������
���� � ����� ��������� �� ��� ����� ���� �������.


����� 3. �������������� ��������

3.1. ������
���� � ���������� ���������

������ ��������� ������ ���������� � ���������� �������
main(). ����� ���������� ����
������������������ ������ ��������� ��������� ���� ������.
������ ����������� �����. ��� ������ �������� ������ �
���������� ������� init_module, ��� �
�������, ������� �� ���������� ����� ����� module_init. ��� ������� ������� ������,
������� �������������� ��� ��� ����������� �������. �����
���������� ���������� ������� init_module ������ ������ ������ �� ������, ��
������ «����� � ����», ����� ���� ��������� � ����
��� ���������� ������������� ��������.

������ ����� ����� � ������ — cleanup_module, ����������
��������������� ����� ��� ���������. ��� ����������
«�����» ���������, ����������� �������� init_module() �, ��� �� ������� ����: «�
�����! ������ �� ����� ���� �� � ���!».

����� ������ ����������� ������ ����� ������� �������������
� ������� ����������. ��� ��� ���������� ����� ��� ���� ������
���������� ������� ������������� � ����������, � ���� ���������
������������ ������� «���������» �
«��������» �������, ���� � ������� � ����� ��������
init_module � cleanup_module, �� �����, ��� �� ������� ����
���������.


3.2. �������, �������
�������� �� �������

��� �����������, �� ������, ��� ���������� ����� ��������
�������, ������� �� ���������� � ����� ���������. �� ������
���������� (��������) ����������� ��� ������� ������, ��������
�� ������� ����������. ������� printf — ���� �� ����� �������,
������� ���������� � ���������� libc.

������ ���� � ���� ����� ������ ���������� �� ����������
��������. � ������� «Hello World» �� ������������
������� printk(), �� �� ����������
����������� ���������� �����-������. ������ ��� �� ��������
������ ����������, �� ������ � �����, � ����� �������� ������
�� �������, ������� �������������� �����. ���������� ������ ��
������� ������� ������������ �������� insmod. ���� � ��� ���� ������� ��������� ��
������ ����, �������������� �����, ��������� � ���� /proc/kallsyms.

����� � ���� ��������� ���� �������� �� ��������� �����
������������� ��������� � ���������� ��������. ������������
������� — ��� ������� �������, ������� �������� � ������������
������������ � ������������ ����� ������� ��������� � ��������,
������� ��������� �������� ������ — ��������� �������.
��������� ������ �������� � ����������������� ������ �� �����
������������ � ��������������� ����� �����. ������������
������� printf() �� ������ ������
�������� ��� �������� ������� ������, �� ���, ��� ���
���������� ������ — ��� ��������� ������, � ������������ �
�������� ��������, � �������� �� ��������������� ����������
������ write(), ������� � �������
������ �� ���������� ������������ ������.

��� � ���� ����� ���������? �� ����� ������! �������������
��������� ���������:

#include <stdio.h>
int main(void)
{ 
  printf("hello"); 
  return 0; 
}  
    

� ������� ������� gcc -Wall -o hello
hello.c
� ��������� �� �������� strace hello. ����������? ������ ������,
��������� �� �����, ������������� ���������� ������.
strace — �����������
���������� ��� ����, ����� �������� — ���� ���������, ��������
����������, ������� ����� ��������, ��� ����� ���������
�������, ������������ �� ��������� � ������������ ��������.
����� �� ������ ������� ������, ������� �������� �������� ���:
write(1, «hello»,
5hello). ��� � ���� ��, ��� �� ����. �.�. ������� �� ���
������� ������ ������� printf().
�������� �� �� ������� � ������� write(), ��������� ����������� �������������
������������ ������������ ������������ ������������� ���������
(������ ��� fopen(), fputs(),
fclose()). ���� ��� ���, ����� ��������� � man 2 write. ������ ������ �����������
����������� �������� �������� ��������� ������� (����� ���
kill(), read() � �.�.). � �������
������� ����������� ������������ ������ (����� ��� cosh(), random() � ��.).

�� ������ ���� �������� ������, ������� ��������� ���������
������ ����, ������ �� ���������������� ���. ��������� ��������
����� ���������� ��� ����������� ��� �������� «�������
����» � ������� ��� «�������», �� �� ������
������������ �� � ����� ����������� �����, �������� ���������
���� �������� ������ «Tee hee, that tickles!»
(«��-��, �������!») ������ ���, ����� ��� ������
������� ������� ����.


3.3.
������������ ������������ � ������������ ����

�� ������ � �������� ������� �������� ����, ���� ��
����������, ������� ���� ��� ���� ������. ��������� �����
����������� ����� ����� �� ������ � ��� ��� ���� ��������.
��������, ��� ���������� ����� ���������, � �������� ���� �
������� �� ������� ����, ��� �� �������� updatedb, ����� �������� ��������� ����
������. � ���������� ��� vim
updatedb ��������
������������� �� ��������� ������� ������. ���� ������
��������� ������������� �������, � «���������» �� �
������� �����������. � ���� �� ��� ����������� ��������� �����
�������� � ��������� �������. ������ �� ������� ����� ����
������� «�������» ��������. �������������� Intel
80386 ����� ������ ����� ������, ������� ����� ��������
«��������». Unix ���������� ������ ��� �� ���:
��������� (������� ������, ��������� ��� �� ��� ���������
�����������������
�����
) � ������ (���������������� �����).

�������� � ���������� ������������ ������� � ���������
�������. ��� �������, ��������� ���������� � ������������
��������, �������� � ���������������� ������. �����
������������ ������� ���������� � ��������� �������. ���������
������ ��������� �� ����� ������������ �������, �� �������� �
�����������������
������
, ��� ��� ��� �������� ����������������
������ ����. ��� ������ ��������� ����� ��������� ���� ������,
�� ���������� ���������� ������������ ������� � ����������
�������� ������� � ���������������� �����.

������, � ������� ����������, �� ������� ��� � ������������ ����
������������
������������
. ��� ��� ������� ���������� �� ������
��� ������ ����������, �� ��� �� � ��, ��� ������ �� �������
����� ���� ����������� ����������� ������ — ���� �����������
�������� ������������.

Unix ���������� ������������ �� ������������
������������
������������ ���� ������ ���, �����
���������� ������ ��������� ����� ��� ������������������
���������� �����������. ��� ����, ����������� ��������� �����,
�������� � ��������� �������� — �� ����� ���������� �������� �
����� ������ � ������ � �������� ������������ ��������. ���,
������� ������������ ����������, ��������, ������� �����������
�� ����� �������, �� ��������� �� � ������ �� ���������.

�������� ���������� ������� — ���������� ����������������
����. ��� ������ ����������� � ������������ ����.
������ ������ ��������� ���, ������������� ���� ������ — ����
������� ����������� ��� ����� ��������� �������, ������ —
���������� ��������� ����������.


3.4. ������������
����

����� �� ������ ��������� ���������� �� C, �� �������� ����
������� � ���������� ���, ��� ��� ��� ������. � ������ �������,
����� �� �������������� ����� ����������� �������, �������� �
������ �������� ������������ ������, ����� ����������
����������, ������� �� �������, ���������� ������ ����� ������
���������� ���������� ������. � ���� �������� ����� ����������
��������� ����. ��������� �������� ���������� ���� ������� �
����������, � ���������� �������� ���������, �������� �������
�� ��������� � ���������������, �������� �
«�����������» ������������ ����. �����������, �������
�������� � ������ ������������, ������ �������� ����������
���������� ��� �� ��, ����� ���������
«�����������������» ����� � ��������� ���� ����������
��������.

������ ���� ����������� � �������� ����������� ������� —
�����, ������� �������� «�����������» ������������
���� ���������� ���������� ������. �������� ���� ����� ��������
�������������� ������, ������� �� ����, ��� ������ ������
������������ �����������, � ���������� ������ ������������
�����������. ������ ������ �������� «�����������»
������������ ���� — ��� ��������� ��� ����� ��� static � ������������ �������� ��� ��������
������������ ������ � ���������� �������� ���������. ��
���������� �� ����������, ����������, � �������� ���������,
������������ ������� ������� ��������. ���� �� �� ������
�����-�� ����� �������� ��� static,
�� ��������� �������� ����� ����������� �������� symbol table � ����������� �� � ����. ��� ����
�� ������� ����.

���� /proc/kallsyms �������� ���
����� � ����, � ���������� �������� ���������, ������� ��������
��� ����� �������.


3.5. ��������
������������

���������� ������� — ����� ������� ����, ��� ����������
����� ���������� � ����� «Understanding The Linux
Kernel», ���������� ������������� O’Reilly. �� ��
���������� ������ �� ��� ��������� � ������� ����������
�������, �� ��� ������������� ���������� ����� ���������
�����.

���� �� ������� �� ������������ ��� ���, ��� �������� �����
segfault, �� ��� ��� ������ ����� �������� ��������� ��� ����,
��� ��������� ���������� �� ��������� �� �����-�� ��������
������� ���������� ������. � ����� �����, ��� ������ ��
�������� ���������. ����� ����������� �������, ���� ��������
��� ���� ����� ���������� ������ � �������� ��� ��������. ���
������ ������������ ��� ���������� ������������ ����, �����,
����������, ������������ «����» � ������ �����, � ���
��������� ����� ������������ �����. [3] ���
������ ���������� � ����������� ������ #0 � ������������ ��
���� ������, ������� ���������. ��������� ������� ������,
���������� ��� ������ ���������, �� ������������, �� ������
�� ���������, ����������� � ������ ������ � �������, ������
0xbffff978, ������� ������
�� ��������� �������� ���������� ������! � ����� ������
����� 0xbffff978 �����
������������� ��� �������� ������������ ������ �������
������, ���������� ��������. ��� ������� ���������, ��������
����� «Hello World», �� ����� ���������� � ������,
���������� ������ ���������, ���� ���������� �������� ����,
����������� �������� �����, �� ������� ���� ��� ���� ���
����� �������� ����������.

���� ���� ����� ���� ����������� �������� ������������.
��������� ������ �� ���� �������� ������ ����, �� �� ��� ��
�������� � �������� ������������ ����. ���� ������ segmentation fault, ����������� �
���������� ����� ���� ��������� � ��������� ��� ������ �������,
�� � ������ �������� ������ ����� ����� ��������� ��� ����
�������. ��-�� �������������� ������ � ������ �� ��������
«���������» ����. ��������� ����� ���� �����
���������. ������� ������ ��������� �����������!

�������� �� ��������, ��� ��� ����������� ��� �����
������������ �������, ������� ��������� �� ���������� ����.
[4]
���� ������������ �������, � ������ ������� ����� ���������.
� ����� �� ������ ������ �������� ���� ��������
������������. ��������� ����� ������� GNU Hurd � QNX
Neutrino.


3.6. ��������
���������

�������� ��������� �������� ����� �� �������������� �������
����. ��� ������ ������ ����. ��� ��������� «������
�����», ������� ��������� �������� ������, ����������
������ ����������, � ������������� ������ ����������� ���������
��� ������ � �����������. � Unix ������ ���������� ����������
������������ ������������ (������ ����������) � ��������
/dev. ���� ���� ������������ ��������
�������������� � �����������. ���, ��������, ������� ��������
����� es1370.ko ��������� ����
���������� /dev/sound �� ��������
������ Ensoniq IS1370. ���������������� ����������, ��������
mp3blaster ����� ������������
��� ����� ������ /dev/sound, ������
�� ���������� � ���� ������������� �������� �����.


3.6.1.
������� � ������� ����� ����������

������� �������� �� ��������� ����� ���������. ����
����������� �� �� ���, ������� ������������ ������ ���
������� �� ��������� ������� �����:

# ls -l /dev/hda[1-3]
brw-rw----  1 root  disk  3, 1 Jul  5  2000 /dev/hda1
brw-rw----  1 root  disk  3, 2 Jul  5  2000 /dev/hda2
brw-rw----  1 root  disk  3, 3 Jul  5  2000 /dev/hda3
      

�������� �������� �� ������� � �������, ������������
�������? ������ ����� �������� «������� �������»
����������. ������ — «������� �������». �������
����� ������� � ���, ����� ������� ������������ ���
������������ ����������� �����������. ������ ������� �����
���� ���������� ������� �����. ��� ����� ��������� �
���������� ������� ������� ����������� ����� � ��� ��
���������. ��� �� ���� ������������� ������ ��������� �����
������� �����, ������ 3, ������ ��� ��� ��� ����������� �����
� ��� �� ���������.

������� ����� ������������ ���������, ��� ����������
���������� �������, �������� �� ���������. ����������� �
������� ����, �������, ��� ���� ��� ��� ����������
������������� ����� � ��� �� ���������, ��� �� ����� ������
�� ��� ����� ���������� ������� �����, ������� �������
«�����» �� ��� ��������� ���������� ����������.

���������� �������������� �� ��� ������� ������ —
�����������������.
�������� �������� ������� � ���������� ��������� ������� �
���, ��� ����� ������� � ������� ����������� ������������
�������� ���� — �������. ��� ����� ���������� �����,
��������� ���� ���������� �������� ������. � �����������
Unix-������ ������ ������ ����� ����� 1 ��������� ��� �������
�����, ����������� �������� ����� 2. ���������� �� ����������
— ��� ���� ������ �������� ����������, �� ������� ������
������� ���������������, ���� �� ������. �����������
��������� ��������� � ������ ����������, ��������� ��� ��
���������� �������� ����� � �� ��������� � �����������. ����
������ ������ � ������, ���������� �������� ls-l /dev, ‘b’, ����� ��� �������
����������, ���� ‘c’, ����� — ����������.
����������, ������� ���� ��������� � ������� ���� — �������.
���� ���������� ������ ��������� ���������� ���������
(���������������� �����):

crw-rw----  1 root  dial 4, 64 Feb 18 23:34 /dev/ttyS0
crw-r-----  1 root  dial 4, 65 Nov 17 10:26 /dev/ttyS1
crw-rw----  1 root  dial 4, 66 Jul  5  2000 /dev/ttyS2
crw-rw----  1 root  dial 4, 67 Jul  5  2000 /dev/ttyS3
      

���� ��� ��������� ������, ��� ����������� ������� ������
���������, ��������� � ���� /usr/src/linux/documentation/devices.txt.

��� ����� ��������� ��������� � �������� ��������� �������
� ������� ������� mknod.
����� ������� ����� ����������, �������� � ������
«coffee», �� ������� ������� 12 � ������� �������
2, ����� ��������� ������� mknod
/dev/coffee c 12 2
. ��� ����� �� ��������� ���������
����� ��������� � �������� /dev,
��� �� �����, �������� ��� � ������������ � ���������
������������. ������, ��� ���������� �������� ����������, ��
������ �������, ��������� ���� ���������� � ����� ��������
�������� — �������� �� ����� �� � ������ ����. ������������
— �� �������� ��������� ����� ��� ���������� �����
���������� ����� ����, ��� ������� ����� ���������.

��� ��������� ���������, ������� ���� �� ��������
����������� ����, �� ������� ��� �������� �� �������. �����
���������� ��������� � ����� ����������, ���� ����������
������� ����� �����, ��� ����������� ��������, ������� ������
���������� ��� ���������. ��� ��������, ��� ���� �
���������������� �� ���������� � ���� ������ �� ����� �
������� ������. ������������, ��� ���������� ���� — ��� ���
�������. �� ���������� ������� �����, ����� �������� ������
���������� ����������.

����� ������, ����� � ������ «����������», �
������������ ����� ����� ����������� ���, ������, PCI �����,
������� �� ������ ��������� � ����. ��������� �� ��� ���
����� ���������:

% ls -l /dev/fd0 /dev/fd0u1680
brwxrwxrwx   1 root  floppy   2,  0 Jul  5  2000 /dev/fd0
brw-rw----   1 root  floppy   2, 44 Jul  5  2000 /dev/fd0u1680
      

� ���������� ������� �� ������ ������� �� ���� ������
���������, ��� ��� ��� — ������� ����������, ���
������������� ����� � ��� �� ��������� (������� ����� 2). ��
������ ���� �������, ��� ��� ��� ������������ ��� ��������
��� ������ ������, �������� �� ��, ��� � ��� ����� ������
���� ��������. �� ������ ��� �����? � ���� ��� � ���, ���� ��
��� ������������ �������� ��� ������, �������� 1.44 ��.
������ — ��� �� ����� ��������, �� ��� ������ �������� 1.68
��, � ������������� ����, ��� ��������� ���� ��������
«����������������������» ������
(«superformatted» disk). ����� ������� �����
������� ������� ����� ������, ���
����������-����������������� �������. ��� ��� ������, �����
��� ����� ����������, � ��������� �������� ��������,
���������� ������������ ���� � �� �� ���������� ����������.
��� ���, ����� «����������», � ����� ����������,
����� �������� ����� ����� �����������.


����� 4. ����� ����������
���������

4.1. ��������� file_operations

��������� file_operations ���������� � ����� linux/fs.h � �������� ��������� �� �������
��������, ������� �������� �� ���������� ��������� �������� �
�����������. ��������, ����������� ����� ������� �����������
���������� ��������� ������� ������ ������ �� ����������. �����
���� �������, ����� ����� �������, �������� � ���������
file_operations. ���� ���������� ����������� ���������, ������
�� �������� ������� ���� 2.6.5:

struct file_operations {
        struct module *owner;
         loff_t(*llseek) (struct file *, loff_t, int);
         ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
         ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
         ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
         ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,
                              loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int,
                      unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
         ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,
                          loff_t *);
         ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,
                           loff_t *);
         ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,
                             void __user *);
         ssize_t(*sendpage) (struct file *, struct page *, int, size_t,
                             loff_t *, int);
        unsigned long (*get_unmapped_area) (struct file *, unsigned long,
                                            unsigned long, unsigned long,
                                            unsigned long);
};

    

������� �������� ��������� ������ �� ��� �������,
��������������� ������ ����������. ��������, �������, �������
����������� ����������, �� ������ ��������� �������� ������
�������� (readdir). ���� ���������,
��������������� ��������������� ��������, �����������
«�������» ����������� — NULL.

���������� gcc
������������� ������������ �������� ������� ������ ����������
����� ��������� � �������� ������. �������, ���� �� ���������
�������� ����� � ����������� ���������, ����� ��� ��� ��
��������. ���� ���������� ������ ��������� ����������:

struct file_operations fops = {
        read: device_read,
        write: device_write,
        open: device_open,
        release: device_release
};
    

������, ���������� ��� ���� ������ ���������� ��������,
������� ����������� ���������� C99. ������ ���� ������ �����
��������������. gcc 2.95, ������� � ���������, ������������
��������� C99. ��� ��� �� ������� �������������� �����
����������, ���� �� ������� ���������� ������������� ������
��������:

struct file_operations fops = {
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release
};
    

�� ��� ������ ��� �������� ���������� ��������. � ���, ��
������ �����, ��� � ����� ���� ���������, ������� �� ���� ��
���������������, ���������� gcc ������� «������» ��������� —
NULL. ��������� �� struct
file_operations ������ ������� ��� fops.


4.2.
��������� file

������ ���������� ������������ � ���� ���������� file, ������� ���������� � ����� linux/fs.h. ��� ��������� ������������
������������� ����� � ������� �� ������������ �����������
�����������, ����������� � ������������ ������������. ���
���������� �� �� �� �����, ��� � FILE, ������������ ����������� glibc � ������� � ���� ������� � ���� ����� ��
������������. ��� ��������� ����� ������ � �����������,
��������� ��� ������������ ���������� ��������� �����, � ��
����� �� �����, ������� ������������ ��������� inode.

��� ������� ��������� �� ��������� file �������� filp.

��������� � ������������ ���� � ���������� �����������
��������� file. ����������� ���������
����� ���������, �������� struct dentry
*f_dentry, �� ������������ ���������� ���������, � ��
������ ������������ ��. �������� �� ��������� ���������
file ���������������, ��� ������
���������� ���������, ������������ � ���.


4.3.
����������� ����������

��� ��� ���������� �����, ������ � ���������� �����������
�������������� ����������� ������ ���������, ������� ���
������� ������������� � �������� /dev. [5]
������� ����� ���������� ������� � ���, ����� ������� �
����� ������ ���������� ������. ������� ����� ������������
����� ��������� ��� ������������� ����������, ���� ��
����������� ��������� ����� ���������.

���������� �������� � ������� ������������� ��� �����������
� ����. ��� �������� — ��������� �������� ������ � ������
������������� ������. �������� ��� ����� ������� �������
register_chrdev(), ������������ �
����� linux/fs.h:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);    
    

��� unsigned int major — ���
������������� ������� ����� ����������, const char *name — �������� ����������,
������� ����� ������������ � /proc/devices � struct
file_operations *fops — ��������� �� ������� file_operations ��������. � ������ ������,
������� register_chrdev() ����������
������������� �����. �������� ��������: ������� �����������
�������� �� ���������� ������� ����� ����������. ��� ������,
��� ���� �� ����������� ��� — ��� ����������� ��������.

� ������ ������: ��� �������� ������� ����� ��� ������
����������, ����� �������� �� «������» ���
������������? ����� ������� ������ — ��������� � ����
Documentation/devices.txt � �������
���� �� ��������������. �� ��� �� ����� ������ �����, ������
��� �� ������� �� ������ ������� � ���, ��� ��������� ����
����� �� ����� ������� ���������� ������ � �����-���� ������
�����������. ���������� ����� — «���������» ����
�������� ��� ������������ ����� ����������.

���� �� ���������� ������� register_chrdev(), � �������� �������� ������,
����� 0, �� ������������ ������������� �������� �����
������������ �����, ����������� ���������� �����, ������� �����
����������. ���� �� ���������� �������� ����� ������� � ���,
��� �� ������� �� ������ ������� ���� ����������, ���������
������� ����� ���������� ��� ������� �� ��������. ��� �� �����,
����� ���������� ��� �������� ������� ���� ��������.

  1. ������� ����� �������� ��������� � ��������� ������ (���
    ��� ������ ������ «Hello World»), � �� �����
    ������� ��������� ���� ����������.

  2. ��� ����� ������������������� ����������, � �����
    /proc/devices �������� ������. ��
    ������ ����� ��� ������ � ������� ������� ���� ����������
    ��� ����� �������� ��������� ��������, ������� �������� ���
    ������ �� ���.

  3. ����� «���������» ��� ������� ��������� ����
    ����������, � ������� ���������� ������ mknod, ����� �������� �����������. � ������
    cleanup_module() �������������
    ����������� �������� ����� ���������� � ������� rm.


4.4.
���������� ����������

�� �� ����� ��������� ��������� ������ �� �������
�����������������. ���� ���� ���������� ������� ����� ���� ���
�� ����� ������ ���������, �� ����� ���������� �������� �����
������� ���������� ���������� � ������������ �������� (� �����
������ ������� ���� �� �����������, ��� ����� ����� ���������).
� ���������� ���������� ������� ��������� � ���� �������
������, ��� ����� ���������� ������� ��������� �������. ����
��� �������, �� ���� ������� ������ �������� �� �������� �����
� �� �������� ��������� �� ������. ���� �� ������� — ��
���������� ������� � �������� «�����» �������.
��������� ������ «������» ������ �����������
�������

������, ���� �����-�� �������� ������ ���� ����������,
������� ���������� ��� ������ (������������� �����). � ������ �
�������� cleanup_module() ���
����������, ��������� ��� �� ����� ������������� ��������.
������, ��� ������� ������ � ������� ������� ������� ���������,
������� ������ ����� ���������, ������������ ������. �� ������
������� ��� ����� � ������� ����, � ����� /proc/devices. ���� ��� ���� �� ����� ����, ��
rmmod �� ������ ���������
������. �������� ��������: ��� ��� ����� ������� �� ����������
�������� � cleanup_module(), ���
������ �������, ������ ���������� ������ sys_delete_module (����������� ������� ��
������� � ����� linux/module.c). ��
�� ������ �������� �������� �������� ��������, ��� �� �����,
���� ������������� � ���� ������������ �������, �������
����������� � ��������� �������� �������� ���������:

  • try_module_get(THIS_MODULE):
    ����������� ������� ��������� �� 1.

    try_module_put(THIS_MODULE):
    ��������� ������� ��������� �� 1.

����� ����� ��������� ������ �������� ��������! ���� ��
�����-���� ������� ��������� �������������� ��������, �� ��
������� �� ������� ��������� ������. ���, ����� ��� �������� �
�������, ������� ������ ������������! ��� ����������� ���������
� ����, ���� ��� ������, ��� ���������� ������-���� ������!


4.5. chardev.c

��������� ������ ������� ���������� � ������ chardev. �� ������ ������ ���������� �����
���������� � ������� ������� cat ��� ��������� ��� �� ������ �� ���������
(�������� open()). ����������� �����
����� ������� ����� �������� � ���������� ������� ��������� �
����. ������ �� ������������ �������� ������ (����:
echo «hi» >
/dev/chardev
), �� ���������� ����� ������� � ��������
������������ � ���, ��� �������� ������ �� ��������������.

������ 4-1. chardev.c

/*
 *  chardev.c: ������� ���������� ����������, ��������� ������ ��� ������
 *  ���������� ���������, � ��������� ���������� ������������� ������� ������ �� ����� ����������
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>        /* ����������� ������� put_user */

/*  
 *  ��������� �������, ������ �� ������� � ������������ ���� (.h)
 */
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0
#define DEVICE_NAME "chardev"   /* ��� ����������, ����� ������������ � /proc/devices   */
#define BUF_LEN 80                      /* ������������ ����� ��������� */

/* 
 * ���������� ����������, ��������� ��� static, ����������� ���������� ����. 
 */

static int Major;             /* ������� ����� ���������� ������ �������� */
static int Device_Open = 0;   /* ���������� �������?  
                               * ������������ ��� �������������� �������������� 
                               * ��������� �� ���������� ��������� */
static char msg[BUF_LEN];     /* ����� ����� ���������� ����� ��������� */
static char *msg_Ptr;

static struct file_operations fops = {
  .read = device_read,
  .write = device_write,
  .open = device_open,
  .release = device_release
};

/*
 * �������
 */

int init_module(void)
{
  Major = register_chrdev(0, DEVICE_NAME, &fops);

  if (Major < 0) {    
    printk("Registering the character device failed with %dn",
            Major);
    return Major;
  }

  printk("<1>I was assigned major number %d.  To talk ton", Major);
  printk("<1>the driver, create a dev file withn");
  printk("'mknod /dev/chardev c %d 0'.n", Major);
  printk("<1>Try various minor numbers.  Try to cat and echo ton");
  printk("the device file.n");
  printk("<1>Remove the device file and module when done.n");

  return 0;
}

void cleanup_module(void)
{
  /* 
   * ���������� ���������� 
   */
  int ret = unregister_chrdev(Major, DEVICE_NAME);
  if (ret < 0)
    printk("Error in unregister_chrdev: %dn", ret);
}

/*
 * �����������
 */

/* 
 * ����������, ����� ������� �������� ������� ���� ����������, �������� ��������
 * "cat /dev/chardev"
 */
static int device_open(struct inode *inode, struct file *file)
{
  static int counter = 0;
  if (Device_Open)
    return -EBUSY;
  Device_Open++;
  sprintf(msg, "I already told you %d times Hello world!n", counter++);
  msg_Ptr = msg;
  try_module_get(THIS_MODULE);

  return SUCCESS;
}

/* 
 * ����������, ����� ������� ��������� ���� ����������.
 */
static int device_release(struct inode *inode, struct file *file)
{
  Device_Open--;                /* ������ �� ������ ��������� ������ ������� */

  /* 
   * ��������� ������� ���������, �����, ����� ������ �� ������� ������� ������� ���� ����������,
   * �� ������� �� ������� ��������� ������. 
   */
  module_put(THIS_MODULE);

  return 0;
}

/* 
 * ����������, ����� ������� �������� ��������� ��� �������� ���� ����������
 */
static ssize_t device_read(struct file *filp, /* ��. include/linux/fs.h   */
         char *buffer,                        /* �����, ���� ���� �������� ������ */
         size_t length,                       /* ������ ������ */
         loff_t * offset)
{
  /*
   * ���������� ����, ���������� ���������� � �����
   */
  int bytes_read = 0;

  /*
   * ���� �������� ����� ���������, 
   * ������� 0, ��� ������� ����� �����
   */
  if (*msg_Ptr == 0)
    return 0;

  /* 
   * ����������� ������ � �����
   */
  while (length && *msg_Ptr) {

    /* 
     * ����� ��������� � ������������ ������������ (� �������� ������), 
     * � �� � ������������ ����, ������� ������� ������������ ����� �����������. 
     * ��� ����, ����� ����������� ������, �� ���������� ������� put_user, 
     * ������� ��������� ������ �� ������������ ���� � ������������ ������������. 
     */
    put_user(*(msg_Ptr++), buffer++);

    length--;
    bytes_read++;
        }

  /* 
   * � ����������� �����, ������� ������ ���������� ���������� ����, ���������� � �����.
   */
  return bytes_read;
}

/*  
 * ����������, ����� ������� �������� �������� � ����������, 
 * �������� ���: echo "hi" > /dev/chardev
 */
static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
  printk("<1>Sorry, this operation isn't supported.n");
  return -EINVAL;
}
     

������ 4-2. Makefile

obj-m += chardev.o


4.6. ��������
������� ��� ������ � ������� �������� ����

��������� ������, ������� ���� ���� �������� ��������� �
�����, ��� ������� �� �������� ���� ��������� ������ �� ������
� ������. � ���� ����� ���� ��������� ����� ��������� ������,
�� ������, ����������� ������, ��������� ���� ���������,
���������� �� ������ ����. �������� ��� � ����� ����������
�������� �������������, ����� �� �������� ���������� ������
����� ���������� ����������. � ����������� �������, �����
��������� ����� ��������� ���� �� ������. � ������ �������,
���������� ���������� ���� ����� ���������� �� ������ �
������.

������ ���� �������������� �� ����������
(n.<������_�����>.m) � ������������
(n.<��������_�����>.m). ������������ ������ ����� � ����
����� ����� ���������, ������� ��, ������� ����� ���������
������� � ��, ������� ��������� ������������ ��������� �
��������� ������. � ����������, �� �� ������ �������� ���� ���
����� ����������, ��������� �� ����� ��� ���������� (�� ����
������� � �� �������� ������ ��������� �� � ���� ����� —
������� ����� ������, � ���� �� ��������� ���������� �������
������). �� ���������� ������ �� ����� �������, ��� ����������
��������� �����������, ���������� �� ������ ������ (���������
����� � ������ ������ — m).

����, �� ��� ������, ��� ����� ������� �������� ���� �����
������������ ������ ������������ �������. ���� � ��� ��������
������������� � �������� ������, ������� ��� �� �������� �
������� �������� ����, �� ������ ��������������� �����������
�������� ����������, ����������� �� ��������� ����������������
LINUX_VERSION_CODE � KERNEL_VERSION. ��� ������ a.b.c, ������
KERNEL_VERSION ������ ��� ������,
����������� � ������������ � ����������: 2^{16}a+2^{8}b+c.
������ LINUX_VERSION_CODE ����������
������� ������ ����.

� ���������� ������� ������� �����������, �������� ��������
�����������, ��� ������ ������� ����������� ���, �
�������������� �������� �������� ����������. ��, ������� � ����
������, �� ������ ������� � ����������� ���������. ������, ����
�� ������� ������ ������ ��� ������������ ������ ����,
����������� � ��������������� ������ ����������� (LKMPG). ��
������ ��������� ���� �������� ��� �������� (����� ������ �
����� ���������), ������������ � �������� ������������ ����.
����� �������, ������������, ���������� ��� ���� 2.4.x, ������
���������� � LKMPG ������ 2.4.x, ���������� ��� ���� 2.6.x — �
LKMPG ������ 2.6.x � �.�.


����� 5. �������� ������� /proc

5.1.
�������� ������� /proc: ��������
������, ��������� ��� ������

Linux ������������� ���� � ������� ���� ��������������
�������� �������� ���������� ���������������� � ��� ���������
— ��� �������� ������� /proc.
������������� ��� ����������� � ����� ��������� �������� �
��������� (������ ����� ��������). ������ ��� ����������
������������ � ����� �����, �������� ���� ��� ��������!
��������, /proc/modules — ������
����������� �������, /proc/meminfo —
���������� ������������� ������.

�������� ������ � �������� �������� /proc ����� ������ �� ������ ��������� �
������� ���������: �� �������� ��������� �� ���� �����������
�����������, ������� ��������� �� �������-����������� (� �����
������ ������� ������ ���� ����������, ������� �����������
������ ����� � /proc). �������
init_module ������������ ���������, �
cleanup_module ��������
�����������.

�������� �������, �� ������� ������������ proc_register_dynamic [6]
������� � ���, ��� ����� inode, ��� ������ �����, �������
����������, ������� �� ���� ����������� ���� ���������� ���
��������������, ����� ������������� ��������� ���������. �
������� �������� ��������, ����������� �� �����, �� �
������, ��� /proc, inode ���������
�� �� ����� � �������� ������������, ��� ��������� ���������
������ (index node, ���������� — inode) � �����. Inode
�������� ��� ����������� �������� � �����, �������� �����
�������, ��������� �� ������ ���� � ���������� �����.

��������� �� �� ��������������� ��������� ��������
��������/�������� ����� � �������� ������� /proc, �� ��� ������ ��������� ������ �������
try_module_get � try_module_put. ���� ����� �������� ���, ���
������ ��� �������� � �� ����� ��� ��������������� ���� �
/proc ��������� ��������, � ���������
� ��� �� ����� ����������� �������� ��������� �����������. �
��������� ������� �� ��������� � �������� �������, ��
���������� ������ ������� ������ �� �������� ��������.

������ 5-1. procfs.c

/*
 *  procfs.c -  ������ �������� "�����" � /proc
 */

#include <linux/module.h> /* ���������� ��� ������ ������ */
#include <linux/kernel.h> /* ���-���� �� �������� � �����! */
#include <linux/proc_fs.h>/* ���������� ��� ������ � �������� �������� /proc */

struct proc_dir_entry *Our_Proc_File;

/* ���������� ������ �� ����� � /proc.
 * 
 * ���������
 * =========
 * 1. ����� � �������. ��� ��� ��������� -- �� ������� ����
 * 2. ��������� �� ��������� �� ������ ��������. 
 *    ���� �� �� ������� ������������ �����
 *    ����������� �����.
 * 3. ������� ������� � �����
 * 4. ������ ������.
 * 5. ������� ����� �����, "1" == EOF.
 * 6. ��������� �� ������ (��������� � ������ �������������
 *    ����������� �� ��������� ������ � /proc)
 *
 * ������� ������������� � ������������ ��������
 * =============================================
 * ������� �������� == "����� ����", �.�. "����� �����". 
 * ������������� �������� == ��� ������.
 *
 * �������������� ��������
 * =======================
 * �������� �������� ���������� ���� �������
 * � ��������� �� �� ������������, � �� �������� �������
 * �������, ����������� �������� ��������. ���� ������������ �������������
 * ���� get_info � ��������� proc_dir_entry (���� ��� ��� ���������
 * �� ��� ������ � ����������� ��������� find � grep),
 * ������������ ���� ������ � ����� � <kernel source
 * directory>/fs/proc/array.c.
 *
 * ����� ��� ���-�� ���������, �� ����� �����
 * �������� ������� � �������� ������� ����. � ���� �������
 * �������� ������������ Linux ����� ������� ��,
 * ��� ��� ��� �������� ��� �������� ������, ��� ��� -- 
 * ����������� ���� �������������!
 */
ssize_t
procfile_read(char *buffer,
              char **buffer_location,
              off_t offset, int buffer_length, int *eof, void *data)
{
  printk(KERN_INFO "inside /proc/test : procfile_readn");

  int len = 0;          /* ����������� ����� ���� */
  static int count = 1;

  /* 
   * �� ������ ������ �������� ��������� ����������,
   * ���� ������������ ���������� -- �� ������ ��������.
   *
   * ��� ����� �����, ��������� ������������ ������� read
   * ����� ���������� ���������� � ���������� ������
   * read �� ��� ���, ���� ���� �� �������, ��� �������� ������ ���
   * ��� ���� ����� �� ����� ��������.
   */
  if (offset > 0) {
    printk(KERN_INFO "offset %d : /proc/test : procfile_read, 
           wrote %d Bytesn", (int)(offset), len);
    *eof = 1;
    return len;
  }

  /* 
   * ��������� ����� � �������� ��� ������
   */
  len = sprintf(buffer,
          "For the %d%s time, go away!n", count,
          (count % 100 > 10 && count % 100 < 14) ? "th" :
          (count % 10 == 1) ? "st" :
          (count % 10 == 2) ? "nd" :
          (count % 10 == 3) ? "rd" : "th");
  count++;

 /* 
  * ������� ������ ������
  */
  printk(KERN_INFO
         "leaving /proc/test : procfile_read, wrote %d Bytesn", len);
  return len;
}

int init_module()
{
  int rv = 0;
  Our_Proc_File = create_proc_entry("test", 0644, NULL);
  Our_Proc_File->read_proc = procfile_read;
  Our_Proc_File->owner = THIS_MODULE;
  Our_Proc_File->mode = S_IFREG | S_IRUGO;
  Our_Proc_File->uid = 0;
  Our_Proc_File->gid = 0;
  Our_Proc_File->size = 37;

  printk(KERN_INFO "Trying to create /proc/test:n");

  if (Our_Proc_File == NULL) {
    rv = -ENOMEM;
    remove_proc_entry("test", &proc_root);
    printk(KERN_INFO "Error: Could not initialize /proc/testn");
  } else {
    printk(KERN_INFO "Success!n");
  }

  return rv;
}

void cleanup_module()
{
  remove_proc_entry("test", &proc_root);
  printk(KERN_INFO "/proc/test removedn");
}
     

������ 5-2. Makefile

obj-m += procfs.o


5.2.
�������� ������� /proc: ��������
������, ��������� ��� ������

���� �� ����� � ���� �������� ��������� ���������� ��
�������� ����������: ����� ���������������� ������� � �������
���� ����������, � ������� ���� � �������� ������� /proc. ������������ �������� — �� ���� ������
�� ����� �������� ������ ����. ��� ������ ���������
������������ �������� ������ ������ ���� ����������� ��������
������� /proc.

��������� �������� ������� /proc
���� ��������, ������� �������, ��� ���� ����� �������� ������
�� ����, ��� �� ��������������� ����������� ������� ��� ������
������ � �����. ��������� proc_dir_entry �� �������� ���������
�� �������-���������� ������. �������, ������ ����, �����
������ � /proc ��������, �� ���������
����� ������������ �����������, ��� �������� �������,
��������.

Linux ��������������� ����������� ����������� ��������
�������. ��� ��� ������ �������� ������� ������ �����
����������� �������, ��� ��������� inode � ��������� ��������
��������, [7] ��
������� ����������� ���������, ������� ������ ��������� ��
��� ����������� �������-����������� — struct inode_operations, ������� ��������
��������� �� struct
file_operations. �������� ������� /proc, ������ ���, ����� �� ������������
����� ����, ��������� ������� — ����� struct inode_operations ����� ��������������
��� ������� � ����. � ���� �������, � ���� ��������� �������
��������� struct file_operations,
� � ��� ��� ��������� ��������� �� ����
�������-�����������.

�������� ��������: ����������� ������� «������» �
«������», � ���� ����� ��������������� �����. �������
������ ������������ ��� ������ � ����, � �� ����� ��� �������
������ ������������ ��� ������ �� �����. ������� � ���, ���
������� «������» � «������» ���������������
����� � ����� ������ ������������: ���� ������� ������ ���-��
�� ���� — ���� ������ �������� ��� ������, ���� ������� �����
— ���� ������ ��������� ��, ��� ��������.

��� ���� ���������� ������ — ������� module_permission. ��� ���������� ������ ���,
����� ������� �������� ���������� � ����� � �������� �������
/proc, � ��������� ������� —
��������� ������ � ����� ��� ���. �� ����������� ����, �������
����������� ������ �� ������ ����������� �������� � UID
��������, �� � �������� �������� � ���� ����������� ��������
�������, ��������, ��������� �� ������������� ������ � �����
���������� ��������� � ��..

�������, �� ������� ��� ����������� ������ ������������
������� put_user � get_user, ������� � ���, ��� �������� � Linux
(�� ������� ���� � ����������� Intel) ����������� �
������������� �������� �������������, �� �������������� �
�������� ������������� ����. ��� ��������, ��� ���������, ��
�������� ���������� ����� ���������� ������ — �� ������
���������� ����� � �������� ������������ ��������.

������������ �������� ������������, ��������� �������� —
��� ��� ����������� �������� ������������. ����������� �����
������ ����, ������ ����� ����������� ������ ����������� �
����������������� ����������. ������, ����� ������ ����
�������� ��������� �� ����� �����, �� ����� ����� ������
��������� � �������� ������������ ��������. ������������
put_user � get_user ��������� ���������� � ������ ��������
�� ���������� �� ������.

������ 5-3. procfs.c

/* 
 *  procfs.c -  ������ �������� ����� � /proc, ������� �������� ��� �� ������, ��� � �� ������.
 */
#include <linux/module.h>  /* ���������� ��� ������ ������ */
#include <linux/kernel.h>  /* ���-���� �� �������� � �����! */
#include <linux/proc_fs.h> /* ���������� ��� ������ � �������� �������� /proc */
#include <asm/uaccess.h>   /* ����������� ������� get_user � put_user */

/* 
 * ����� �������� ���������� ��������� ���������,
 * ������� ����� ���������� � ����, ����� ��������, ���
 * ������ ������������� ����� �������� ���� �� ������������
 */
#define MESSAGE_LENGTH 80
static char Message[MESSAGE_LENGTH];
static struct proc_dir_entry *Our_Proc_File;

#define PROC_ENTRY_FILENAME "rw_test"

static ssize_t module_output(struct file *filp, /* ��. include/linux/fs.h   */
             char *buffer,      /* ����� � ������� */
             size_t length,     /* ������ ������   */
             loff_t * offset)
{
  static int finished = 0;
  int i;
  char message[MESSAGE_LENGTH + 30];

  /* 
   * ��� ��������� �������� ����� ����� ������������ 0.
   * ���� ����� �� �������, ������� ����� ���������� 
   * �������� ������ �� �����,
   * ������ � ����������� ����. 
   */
  if (finished) {
    finished = 0;
    return 0;
  }

  /* 
   * ��� �������� ������ �� ������������ ���� � ������������ ������������ 
   * ������� ������������ put_user.
   * � �������� ����������� -- get_user.
   */
  sprintf(message, "Last input:%s", Message);
  for (i = 0; i < length && message[i]; i++)
    put_user(message[i], buffer + i);

  /* 
   * �������� ��������: � ������ �������� �� ������� �� �������������,
   * ��� ������ ��������� ������, ��� len, � ��������� ������ ��������� ���� ��������.
   * � �������� ��������, ���� ����� ��������� ������ ��� 
   * len, �� ������������ len, � ������� ��������� ������������ 
   * �� ����������� �������.
   */
  finished = 1;

  return i;             /* ������� ���������� "�����������" ���� */
}

static ssize_t
module_input(struct file *filp, const char *buff, size_t len, loff_t * off)
{
  int i;
  /* 
   * ����������� ������, ���������� �� ������������ � �����, 
   * ������� ������� ����� ������� �������� module_output.
   */
  for (i = 0; i < MESSAGE_LENGTH - 1 && i < len; i++)
    get_user(Message[i], buff + i);

  Message[i] = '';    /* ������� ������, ������������� ��������  */
  return i;
}

/* 
 * ��� ������� ��������� ������� � ����� �� ���������� �������� � ������
 * 0 -- ���������, �������� �������� -- ���������.
 *
 * �������� � ������ ����� ����:
 * 0 - ����������e (�� ����� ������ � ����� ��������)
 * 2 - ������ (�������� �� ������������ � ������ ����)
 * 4 - ������ (�������� �� ������ ���� � ������������)
 *
 * ��� ������� ��������� ����� ������� � �����
 * �����, ��������� �������� ls -l 
 * ����� ���� ��������������� �����.
 */

static int module_permission(struct inode *inode, int op, struct nameidata *foo)
{
  /* 
   * �������� ������ ������ ����, ��
   * ������ -- ������ root-� (uid 0)
   */
  if (op == 4 || (op == 2 && current->euid == 0))
    return 0;

  /* 
   * ���� ���-�� ���� -- ��������� ������
   */
        return -EACCES;
}

/* 
 * ���� ������ -- ���� ��� ��� ����� ������������ � ���-��
 * ������������, ��� ����� ������� -- ��� ��������� 
 * ������� ��������� � ������.
 */
int module_open(struct inode *inode, struct file *file)
{
  try_module_get(THIS_MODULE);
  return 0;
}

/* 
 * ���� ������ -- ��������� ������� ���������.
 */
int module_close(struct inode *inode, struct file *file)
{
  module_put(THIS_MODULE);
  return 0;             /* ��� ���������! */
}

static struct file_operations File_Ops_4_Our_Proc_File = {
        .read = module_output,
        .write = module_input,
        .open = module_open,
        .release = module_close,
};

/* 
 * �������� ��� ��������� ������� ������ �����. ����������
 * ��� ����, ����� ������� �������������� ���������
 * file_operations ������ �����, � ��� ��, ����� ������ �����
 * ������� ����������� ���� ������� � �����. ����� ����� ������� ������
 * ������ �������-������������, �� ��� ��� �� ����������.
 */

static struct inode_operations Inode_Ops_4_Our_Proc_File = {
  .permission = module_permission,      /* �������� ���� ������� */
};

/* 
 * ��������� � �������� ������� ������
 */
int init_module()
{
  int rv = 0;
  Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL);
  Our_Proc_File->owner = THIS_MODULE;
  Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File;
  Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File;
  Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR;
  Our_Proc_File->uid = 0;
  Our_Proc_File->gid = 0;
  Our_Proc_File->size = 80;

  if (Our_Proc_File == NULL) {
    rv = -ENOMEM;
    remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
    printk(KERN_INFO "Error: Could not initialize /proc/testn");
  }

  return rv;
}

void cleanup_module()
{
  remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
}
     
     

������ ��� ������� ������ � �������� �������� /proc? ������, �� ������ �����, ����� �����,
��� /proc ������ � ������� � ������
��� ������� ������������ sysfs. �������������� �������� �
�������� ������� /proc �� ������� �
linux/Documentation/DocBook/. �����
������� make help, ��� �������
���������� �� �������� ������������ � ��������� ��������,
��������: make htmldocs.


����� 6. ������ � �������
���������

����� ��������� ������������ ���������� ����������. �
����������� �����, ���������� ���������� ������������ ��� ���
������, ��� � ��� �����, ����� ������� ���������� ����� �����
�������� ��� �������� ������ �� �������� (����� ������ ����) �
����������. ���� �� ��������� — ������� ���� ���������� �
�������� � ���� ������, ����� ��� ��, ��� � ������� ����. �
��������� �������, �������� ������ ����������� ��������
device_write.

������, ����� �� ������ ������ ����������. ��������, ��� � ���
���� �����, ������������ � ���������� ����� ���������������� ����
(��� ����� ���� � ���������� �����, � ����� ������ CPU ��
«��������» ��� �����, ��������� � ����������������
������). ������������ ������� — ������������ ���� ���������� ���
�������� ������ ������ (��� ����� ���� ������� ������ ��� ������,
������� ����� ������� � ���������� �����) � ��� ������ ������ ��
������ (������ ������ �� ������� ��� ������, ���������� ��
���������� �����). ������, ��� ��������� �������� ������ � ���,
��� ����������������� ��������������� � ���������������� ������,
��������, ��� ��������� �������� ������.

�����: � Unix ������� ������������ ����������� ������� �
������ ioctl (���������� �� Input
Output ConTroL). ����� ���������� ����� ����� ���� �������
ioctl, ������� ����� ������ (���
�������� ������ �� �������� ����), ������ (��� �������� ������ ��
���� � ��������), � ������ � ������, � �� �� �� ������, [8]
������� ioctl ���������� � �����
�����������: ���������� ����� ����������, ����� ioctl � ������ ��������, ������� ����� ���
long, ������������ ��� �������� �������������� ����������.
[9]

����� ioctl �������� ���������� ���, ������������ �������
����� ����������, ��� ������� � ��� ��������������� ���������.
������ ����� ioctl ��������� ����������������� (_IO, _IOR, _IOW
��� _IOWR, � ����������� �� ����) � ����� ���������. ����
������������ ������ ������������ ���������� #include, � ��������
������ ���������, ������� ���������� ioctl ��� ������ ������� �
�������. � �������, ���������� ����, ������������ ���� ���������
chardev.h � ���������, ������� ��������������� � �������
ioctl.c.

���� �� ������������� ������������ ioctl � ����� �����������
�������, �� ��� �������� ���������� � ����� Documentation/ioctl-number.txt � ���, ����� ��
«������» ������������������ ������ ioctl.

������ 6-1. chardev.c

/*
 *  chardev.c - ������ �������� ����������� ���������� 
 *              ���������� �� ������/������
 */

#include <linux/module.h> /* ���������� ��� ������ ������ */
#include <linux/kernel.h> /* ���-���� �� �������� � �����! */
#include <linux/fs.h>
#include <asm/uaccess.h>        /* ����������� ������� get_user � put_user */

#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80

/* 
 * ���������� ��� �������? ������������ ���
 * �������������� ������������� �������� � ����������
 */
static int Device_Open = 0;

/* 
 * ����� ���������� �� ������
 */
static char Message[BUF_LEN];

/* 
 * ������� � ������.
 * ������������ � ��� ������, ���� ��������� ���������� �������
 * ��� ������ ������. 
 */
static char *Message_Ptr;

/* 
 * ���������� ����� ������� �������� ������� ���� ����������
 */
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk("device_open(%p)n", file);
#endif

  /* 
   * � ������ ���������� ������ ������� ������ ���� ������� ����� ������� ���� ���������� 
   */
  if (Device_Open)
    return -EBUSY;

  Device_Open++;
  /*
   * ������������� ���������
   */
  Message_Ptr = Message;
  try_module_get(THIS_MODULE);
  return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk("device_release(%p,%p)n", inode, file);
#endif

  /* 
   * ������ �� ������ ������� ������ �� ������� ��������
   */
  Device_Open--;

  module_put(THIS_MODULE);
  return SUCCESS;
}

/* 
 * ���������� ����� �������, ��������� ���� ����������
 * �������� ������� �� ���� ������.
 */
static ssize_t device_read(struct file *file, /* ��. include/linux/fs.h   */
            char __user * buffer,             /* ����� ��� ��������� */
            size_t length,                    /* ������ ������       */
            loff_t * offset)
{
  /* 
   * ���������� ����, ���������� ���������� � �����
   */
  int bytes_read = 0;

#ifdef DEBUG
  printk("device_read(%p,%p,%d)n", file, buffer, length);
#endif

  /* 
   * ���� ��������� ����� ��������� -- ������� 0
   * (������� ����� �����) 
   */
  if (*Message_Ptr == 0)
    return 0;

  /* 
   * ���������� ������ ������ � �����
   */
  while (length && *Message_Ptr) {

    /* 
     * ��������� ����� ������������� � ������������ ������������,
     * ������� ���������� �� ���������. �������
     * ��� ������ ������ ������������ put_user,
     * ������� �������� ������ �� ������������ ����
     * � ������������ ������������.
     */
    put_user(*(Message_Ptr++), buffer++);
    length--;
    bytes_read++;
  }

#ifdef DEBUG
  printk("Read %d bytes, %d leftn", bytes_read, length);
#endif

  /* 
   * ������� ���������� ����, ���������� � �����.
   */
  return bytes_read;
}

/* 
 * ���������� ��� ������� ������ � ���� ����������
 */
static ssize_t
device_write(struct file *file,
            const char __user * buffer, size_t length, loff_t * offset)
{
  int i;

#ifdef DEBUG
  printk("device_write(%p,%s,%d)", file, buffer, length);
#endif

  for (i = 0; i < length && i < BUF_LEN; i++)
    get_user(Message[i], buffer + i);

  Message_Ptr = Message;

  /* 
   * ������� ���������� �������� ����
   */
  return i;
}

/* 
 * ����������, ����� ������� �������� ��������� �������� ioctl ��� ������ ����������.
 * ����� inode � ��������� file ������� �������� ��� �������������� ���������:
 * ����� ioctl � �������������� ���������.
 *
 */
int device_ioctl(struct inode *inode, /* ��. include/linux/fs.h */
      struct file *file,              /* �� �� ����� */
      unsigned int ioctl_num,         /* ����� � ��������� ioctl */
      unsigned long ioctl_param)
{
  int i;
  char *temp;
  char ch;

  /* 
   * ������� �� ��������� ������� ioctl 
   */
  switch (ioctl_num) {
  case IOCTL_SET_MSG:
    /* 
     * ������� ��������� �� ��������� (� ������������ ������������) 
     * � ���������� � �����. ����� �������� ����� � ������������� ���������.
     */
    temp = (char *)ioctl_param;

    /* 
     * ����� ����� ���������
     */
    get_user(ch, temp);
    for (i = 0; ch && i < BUF_LEN; i++, temp++)
      get_user(ch, temp);

    device_write(file, (char *)ioctl_param, i, 0);
    break;

  case IOCTL_GET_MSG:
    /* 
     * �������� ������� ��������� ����������� �������� - 
     * �������� �� ���������� ������.
     */
    i = device_read(file, (char *)ioctl_param, 99, 0);

    /* 
     * �������� � ����� ����������� ������ 
     */
    put_user('', (char *)ioctl_param + i);
    break;

  case IOCTL_GET_NTH_BYTE:
    /* 
     * ���� ����� �������� ������ (ioctl_param) � 
     * ������� (������������ �������� �������) ������������
     */
    return Message[ioctl_param];
    break;
  }

  return SUCCESS;
}

/* ���������� */

/* 
 * � ���� ��������� �������� ������ �������-������������
 * ��������, ������������ ��������� ��� �����������.
 * ��������� ��������� �� ��� ��������� �������� � ������� ���������,
 * ��� �� ����� ���� ��������� ��� init_module. 
 * ������������� ��������� � ��������� ���������� ��������� NULL.
 */
struct file_operations Fops = {
  .read = device_read,
  .write = device_write,
  .ioctl = device_ioctl,
  .open = device_open,
  .release = device_release,    /* ��� �� close */
};

/* 
 * ������������� ������ - ����������� ����������� ����������
 */
int init_module()
{
  int ret_val;
  /* 
   * ����������� ����������� ���������� (�� ������� ���� - ������� �����������) 
   */
  ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

  /* 
   * ������������� �������� �������� ������
   */
  if (ret_val < 0) {
    printk("%s failed with %dn",
           "Sorry, registering the character device ", ret_val);
    return ret_val;
  }

  printk("%s The major device number is %d.n",
         "Registeration is a success", MAJOR_NUM);
  printk("If you want to talk to the device driver,n");
  printk("you'll have to create a device file. n");
  printk("We suggest you use:n");
  printk("mknod %s c %d 0n", DEVICE_FILE_NAME, MAJOR_NUM);
  printk("The device file name is important, becausen");
  printk("the ioctl program assumes that's then");
  printk("file you'll use.n");

  return 0;
}

/* 
 * ���������� ������ ������ - ������������� ����� � /proc 
 */
void cleanup_module()
{
  int ret;

  /* 
   * ������������� ����������
   */
  ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

  /* 
   * ���� ���������� ������ -- ������� ���������
   */
  if (ret < 0)
    printk("Error in module_unregister_chrdev: %dn", ret);
}
     

������ 6-2. chardev.h

/*
 *  chardev.h - ����������� ioctl.
 *
 *  �����������, ������� ����� ���������, ������ ���������� � ������������ ���� ������,
 *  ��� ��� ����������� ��� ������ ���� (chardev.c), ��� � 
 *  ����������� �������� (ioctl.c)
 */

#ifndef CHARDEV_H
#define CHARDEV_H

#include <linux/ioctl.h>

/* 
 * ������� ����� ����������. � ������ ������������� ioctl,
 * �� ��� ������ ����������� ��������������� ������������ �������,
 * ��������� �� ������ ���� �������� �������.
 */
#define MAJOR_NUM 100

/* 
 * �������� �������� ��������� �������� ����������
 */
#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)
/*
 * _IOR ��������, ��� ������� �������� ������
 * �� ����������������� �������� � ������ ����
 *
 * ������ ��������, MAJOR_NUM -- ������� ����� ����������.
 *
 * ������ �������� -- ��� �������
 * (����� ������� ���� ��������).
 *
 * ������ �������� -- ��� ������, ������������ � ����
 */

/* 
 * �������� ��������� ��������� �� �������� ����������
 */
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
/* 
 * ��� ������� IOCTL ������������ ��� ������ ������.
 * ��� �� �������� ����� �����, ����������� � �������� ������������ 
 * ����������� ��������, ���� ��� ��������� ������ ���� ����������.
 */

/* 
 * ������� ��������� n-���� ����� ���������
 */
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
/* 
 * ����� ������� IOCTL �������� ��� �� ����, ��� � �� �����.
 * ��� ��������� �� ������������ ����� ����� (n),
 * � ���������� n-��� ���� ��������� (Message[n]). 
 */

/* 
 * ��� ����� ����������
 */
#define DEVICE_FILE_NAME "char_dev"

#endif
     

������ 6-3. ioctl.c

/*
 *  ioctl.c - ������ ���������, ������������ ioctl ��� ���������� ������� ����
 *
 *  �� ��� ��� �� ����������� �������� cat, ��� �������� ������ �/�� ������.
 *  ������ �� �� ������ �������� ���� ���������, ������� ������������ �� ioctl.
 */

/* 
 * ����������� �������� ������ ���������� � ���� �������� ioctl
 */
#include "chardev.h"

#include <fcntl.h>      /* open */
#include <unistd.h>     /* exit */
#include <sys/ioctl.h>  /* ioctl */

/* 
 * ������� ������ � ��������� ����� ioctl  
 */

ioctl_set_msg(int file_desc, char *message)
{
  int ret_val;

  ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);

  if (ret_val < 0) {
    printf("������ ��� ������ ioctl_set_msg: %dn", ret_val);
    exit(-1);
  }
}

ioctl_get_msg(int file_desc)
{
  int ret_val;
  char message[100];

  /* 
   * �������� - ���� ������� �� ����� -- ����� ����� ����� �� ����������
   * ������� �������� ������, ��������� � ������������� ������.
   * � �������� �������� ��� ���������� �������������
   * �������� � ioctl ���� �������������� ����������:
   * ���������� ������ ��������� � ��� �����
   */
  ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

  if (ret_val < 0) {
    printf("������ ��� ������ ioctl_get_msg: %dn", ret_val);
    exit(-1);
  }

  printf("�������� ��������� (get_msg): %sn", message);
}

ioctl_get_nth_byte(int file_desc)
{
  int i;
  char c;

  printf("N-��� ���� � ��������� (get_nth_byte): ");

  i = 0;
  while (c != 0) {
    c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);

    if (c < 0) {
      printf
        ("������ ��� ������ ioctl_get_nth_byte �� %d-� �����.n", i);
      exit(-1);
    }

    putchar(c);
  }
  putchar('n');
}

/* 
 * Main - �������� ����������������� ������� ioctl
 */
main()
{
  int file_desc, ret_val;
  char *msg = "��� ��������� ���������� ����� ioctln";

  file_desc = open(DEVICE_FILE_NAME, 0);
  if (file_desc < 0) {
    printf("���������� ������� ���� ����������: %sn", DEVICE_FILE_NAME);
    exit(-1);
  }

  ioctl_get_nth_byte(file_desc);
  ioctl_get_msg(file_desc);
  ioctl_set_msg(file_desc, msg);

  close(file_desc);
}
     

������ 6-4. Makefile

obj-m += chardev.o
     

��� ���������� ������ �������, ������������ ������, �������
�������� ��� ������ �� ���:

������ 6-5. build.sh

#/bin/sh

# ������ ����������������� ����������
gcc -o ioctl ioctl.c

# �������� ����� ����������
mknod char_dev c 100 0
     


����� 7. ��������� ������

�� ��� ���, ��� ��� �� ������, ��� ������������ �����
������������ ��������� ���� ��� ����������� ������ � ��������
������� /proc � ������ ���������. ���
��� ������������, �� ��� ������� ������ ��� �������� ���������
���������. � ��� ���� �� ������ ������� ������������� ���-��
���������, �������� �������� ������� ������� �� ����� ����
�������?

�� ��� ��� �������� � �� �������, ��� ����������������
������������� ���������� �������. ��� ���������� �������,
������������ ����, � «���������» ��������� �����
open. � ���������� ������� ��������
����������� ��������� ����� �����, ��� ����������� ������ �������
��������� ����� ���������. � �� ��� ���� ���������� �������
�������� shutdown. ��-�� �����
�������� ���������� � «������» ������ ����������
�������. � �������, �� ���� ���� �� ��� ���������. �����
����������� ���� �� ������ ������, � ����������� ���������, �����
��������� �������� ������� ������ ���������� ���������
�����������.

�������� ��� /proc, �������� � ���
����� ���������. �������� �������� �������������� ���������
� ����� — ��� ��������� ������. ����� ������� �����������
�����-���� ������ ���� (��������, �������� �����, ������ ������
�������� ��� ��������� �������������� ������), ������������
�������� ��������� �������. ���� �� ������ �������� ���������
����, �� ��������� ������ — ��� ��� ��� �� �����, ���� �����
��������� ���� ������ � ������. ����� ������, ���� �� ��������
������� — ����� ��������� ������ ������������ ��� ��� ����
����������, ���������: strace
<command> <arguments>
.

������ ������, ������� �� ����� ������� � ������������ ����.
�� �� ����� ���������� � ������ ���� � �� ����� �������� �������
� ����. �������������� ������������ ������ ���� ������ ��
���������� ������ (��� ������ ����� ���������� ���� ����������
����������, ��� �����������������).

��������� ������ �������� ����������� �� ����� �������. �����
��������� ��������� �����, ������� ��������� ��������
��������������� ���������������� ���������� � ���������
����������� ����������, ������� ���������� ������� �
���������������� ����� � ������������ ���� (����������, �����
�������� �������� ���������������� ��������� �� ������). ���
��������� Intel — ��� ���������� ���������� � �������� 0x80.
�������������� ������������ ��� ��� ������� �� �������������
����������������� ������ � ���������� ����� ����, ��� ���������
������ ���, ��� ��� �����������������.

����� ��������, � ����, ���������� system_call. ���������, ������� ��� ���������,
��������� ����� ���������� ������, ������� �������� ���� — �����
������ ������ ����������� �������. �����, ��� �������������
������� ��������� ������� (sys_call_table), ���������� ����� ������� ����,
������� ������� �������, ����� ���� ���������� ������ �������. ��
��������� ������ ���������� ������, ����������� ���
�������������� �������� � ���� ����� ����� ����������
������������ ����������� �������� (��� ������� ��������, ����
���������� ������� �������� ���� ����� �������). ���, �����������
��� ����������������� ��������, �� ������� � ����� arch/<architecture>/kernel/entry.S, �����
������ ENTRY(system_call).

����, ���� ��� ��������� ������� �������� ��������� ����������
���������� ������, �� ������, ��� ���������� ������� — ���
�������� ���� ����������� �������, ������� ��������� �� ���������
�������� (������, ����� ���������� ����� ��������, � ��������
�������, ���������� �������������� �������, ����������� ���������
�����), ����� — �������� ��������� � sys_call_table ���, ����� �� �������� �� ����
�������. ��������� ��� ������ ������������ ����� ���� ��������,
�� ������� ������������� �������������� ������� � ��
�������������� ���������, ����� �� ��������� �� � ������������
���������. ��� �������� � �������� ������� cleanup_module.

���� ���������� �������� ����� ������ ������. ��
«�������» �� ��������� �������������, � �������� �����
printk ��������� ������ ���, ����� ������ ������������ ���������
�����-���� ����. ��� �����, ��������� ����� open(), ����������� �������� � ������ our_sys_open. ��� ��������� UID (User ID)
�������� ��������, � ���� �� ����� ���������, �� �������� printk,
����� �������� ��� ������������ �����, � � ���������� ��������
������������ ������� open() � ���� ��
�����������, ������� ��������� ��������� ����.

������� init_module ��������
��������������� ��������� � sys_call_table � ��������� ��� ��������������
�������� � ����������. ������� cleanup_module ��������������� ��������� �
sys_call_table, ��������� ���
����������. � ������ ������� ������� ���� «���������
�����» ��-�� ����������� ������������� ���� �������,
������������� ���� � ��� �� ��������� �����. ����������� ����:
������� ��� ������, � � B. ����� ������ A ����������� ���������
����� open, ����� �������� A_open, � ������ B — �������� B_open. ������ ����������� ������ A, �� ��������
��������� ����� open �� A_open. ����� ����������� ������ B, �������
������� ��������� ����� A_open ��
B_open. ������ B ��������, ��� ��
�������� ������������ ��������� �����, ���� �� ����� ���� ���
�������� ����� A_open.

������, ���� ������ B ��������� ������, �� ������ ��������� ��
���������� — �� ������ ����������� ������ � ������� sys_call_table � �������� A_open, ������� � ���� ������� ��������
������������ ������� sys_open. ������,
���� ������ ����� �������� ������ �, � ����� B, �� �������
«������». ������ � ����������� ����� � sys_call_table, ����������� �� ������������
������� sys_open, «�������»
����� ������� ������ B �� ��������� �������� �� �������� ������.
�����, ����� ����� �������� ������ B, �� ����������� ����� �
sys_call_table �� ���, ������� ��������
���, ������ ��� �� ������� ��� ������������. �.�. ������ �����
���������� � ������� A_open, �������
��� ��� � ������. �� ������ ������, �������� ����� ������,
��������� — ��������� �� ����� � sys_call_table � ������� ����� ������� open � ���� �� ���������, �� �� ���������������
�������� ����� ������ (����� ������� B �� �����
«���������������» ��������� �����), �� ��� ���������
������ ��������. ����� ����������� ������ �, ��
«�����», ��� ��������� ����� ��� ������� �� B_open � «������������» ��
�������������� ��������� �� sys_open.
������, ������� B_open ����� ��
�������� �������� �������� A_open,
������� ������ �� ���������� � ������, ��� ��� �������
«������» ��� ������ — �� �������� ������ B.

�������� ��������: �������� �������� ������ �����
«�������» ��������� ������� ������������ ��� ��������
���������������.� ����� �������������� ������������� ���������,
��������� � �������� ������� ��������� �������, ���� ����� ��
������������ sys_call_table. �������,
���� �� ������� ������� ����� �������, ��� ������ ���������
������� �� ������ ������� �������, ��� �������� ��������
«�������» �� ����. � �������� � ��������� �� �������
���� README � «�������». ���
�� ��������� ���������, �������� ����������� ��������� �
������������� �����������, ������� � �� ���������� ����������� ��
�� ��������, ���������� ������� �� �� ��������� ��� �� �
��������� ������ ������������. ���� ��� ��������� ��������, ��
������ ������� ����� ����� �� ������� ����� �������.

������ 7-1. syscall.c

/*
 *  syscall.c
 *
 *  ������ "���������" ���������� ������.
 */

/* 
 * Copyright (C) 2001 by Peter Jay Salzman 
 */

/* 
 * ����������� ������������ �����
 */

#include <linux/kernel.h>      /* ���-���� �� �������� � �����! */
#include <linux/module.h>      /* ���������� ��� ������ ������ */
#include <linux/moduleparam.h> /* ��� �������� ���������� ������ */
#include <linux/unistd.h>      /* ������ ��������� ������� */

/* 
 * ����������, ����� ����� ����������
 * user id ���������� ��������. 
 */
#include <linux/sched.h>
#include <asm/uaccess.h>

/* 
 * ������� ��������� ������� (������� ������� �������). 
 * ������ ��������� �� ��� ������ �� ������� �������.
 *
 * sys_call_table ������ �� �������������� ������ 2.6.x.
 * ���� �� ������������� ���������� ���� ������� ������,
 * ��� ������� �������� "�������" �� ���� � ����������� ���
 */
extern void *sys_call_table[];

/* 
 * UID ������������, �� ������� "�������",
 * ����������� �� ��������� ������
 */
static int uid;
module_param(uid, int, 0644);

/* 
 * ��������� �� ������������ �������, ����������� ��������� �����.
 * �� ��������� ��, ������ ����, ����� �������� �������� ������������
 * �������, ��� ����, ����� ������� ����������� ������
 * ������������, ����������� �� ���
 * ��� �� ����������� 100% ������������, ��������� ������ ������,
 * ���������� sys_open ����� ���� �������� ������ ������ ������.
 *
 * ������ ������� -- �� �� ����� �������� ����� ������������ sys_open.
 * ���� ����� �� �������������� �����.
 */
asmlinkage int (*original_call) (const char *, int, int);

/* 
 * �������, ���������� sys_open (����������
 * ������ ���, ����� �������� ��������� � ���������� ������ open).
 * �������� �������, ���������� ���������� � �� ���
 * �� ������� � fs/open.c.
 *
 * ������������ - �� "���������" � ������ ����������
 * ������ ����. ����������� -- ��������� ������
 * ����� ����� ������������ ������������� ���������,
 * ��������� ��� ������� �� �������� ���������� ������������ �����������
 * ������������� � ����� � ����������� �� �� ����������.
 */
asmlinkage int our_sys_open(const char *filename, int flags, int mode)
{
  int i = 0;
  char ch;

  /* 
   * ��������� -- ��� ������� ������������?
   */
  if (uid == current->uid) {
    /* 
     * �������������
     */
    printk("Opened file by %d: ", uid);
    do {
      get_user(ch, filename + i);
      i++;
      printk("%c", ch);
    } while (ch != 0);
    printk("n");
  }

  /* 
   * ������� ������������ ������ ���������� ������ sys_open - �����
   * ������� �������� ����������� ��������� �����
   */
  return original_call(filename, flags, mode);
}

/* 
 * ������������� ������ - ������� ���������� ������
 */
int init_module()
{
  /* 
   * �������� - �������������� ���������, ��
   * ����� ���� � ��������� ���... 
   */
  printk("I'm dangerous. I hope you did a ");
  printk("sync before you insmod'ed me.n");
  printk("My counterpart, cleanup_module(), is even");
  printk("more dangerous. Ifn");
  printk("you value your file system, it will ");
  printk("be "sync; rmmod" n");
  printk("when you remove this module.n");

  /* 
   * ��������� ��������� �� ������������ �������
   * � ���������� original_call, � ����� �������� ���������
   * � ������� ��������� �������
   */
  original_call = sys_call_table[__NR_open];
  sys_call_table[__NR_open] = our_sys_open;

  /* 
   * ����� �������� ����� ������ ���������� ������
   * � ������ foo, ����������� � ������ sys_call_table[__NR_foo]. 
   */

  printk("Spying on UID:%dn", uid);

  return 0;
}

/* 
 * ���������� ������ ������ - �������������� ��������� �� ������������ ��������� �����
 */
void cleanup_module()
{
  /* 
   * ������������ ����� ���������� ������
   */
  if (sys_call_table[__NR_open] != our_sys_open) {
    printk("Somebody else also played with the ");
    printk("open system calln");
    printk("The system may be left in ");
    printk("an unstable state.n");
  }

  sys_call_table[__NR_open] = original_call;
}
     
     

������ 7-2. «�������» �� ����
(export_sys_call_table_patch_for_linux_2.6.x)

--- kernel/kallsyms.c.orig     2003-12-30 07:07:17.000000000 +0000
+++ kernel/kallsyms.c   2003-12-30 07:43:43.000000000 +0000
@@ -184,7 +184,7 @@
                iter->pos = pos;
                return get_ksymbol_mod(iter);
        }
-       
+
        /* If we're past the desired position, reset to start. */
        if (pos < iter->pos)
                reset_iter(iter);
@@ -291,3 +291,11 @@
 
 EXPORT_SYMBOL(kallsyms_lookup);
 EXPORT_SYMBOL(__print_symbol);
+/* START OF DIRTY HACK:
+ * Purpose: enable interception of syscalls as shown in the
+ * Linux Kernel Module Programming Guide. */
+extern void *sys_call_table;
+EXPORT_SYMBOL(sys_call_table);
+ /* see http://marc.free.net.ph/message/20030505.081945.fa640369.html
+  * for discussion why this is a BAD THING(tm) and no longer supported by 2.6.0
+  * END OF DIRTY HACK: USE AT YOUR OWN RISK */
     
     

������ 7-3. Makefile

obj-m += syscall.o
     

������ 7-4. README.txt

�������� ��������, ��������� � ������ ��������, ������� � �������������
���������� ����� sys_call_table, ��������� �� ����� �� �������������� ������ 2.6.x.

����������� "����������" ��������� ������� ����� sys_call_table  ������������ ������
�������, ������� � ������ 2.5.41, ��� ������ �� ��������������

���������� �������� �� ������� ��:

  http://www.ussg.iu.edu/hypermail/linux/kernel/0305.0/0711.html
  http://marc.free.net.ph/message/20030505.081945.fa640369.html
  http://marc.theaimsgroup.com/?l=linux-kernel&m=105212296015799&w=2


����� ����� ����������� ���������� ������ ������ �� ����� ������ 2.5.41 � ����
��� ���������� �������� ������� �� ����.

��������: 
        �� ����������� ��� ������� �� ������������ ��� ���� ��������,
        ������� �������� ������ ����������.


���� �� � ����� ���������� ������� � ���� ������� � Configure.help
�� � �� ������� �� ��� <dangerous> � ��� �� ��������� ��������:

#######################################################################

��� ����� ������������ sys_call_table, ��� ������ ���������
"����������" (�������) ��������� �������. ������� ��������� ������� ������������ ������
� ����� ����� �������� ������ ����� ��� ��� ����.

������� Y, ���� ������� ���������� ����������� ������
� ��� �� ��������� ��������� ������ ������.

����������� ����� ������ ������ ������� N.

#######################################################################

���� ��� ���������� PC ������������ ������ ��� �������
������ �������� ��� ������� � ���������� ������.

��������������, ��� �������� ������ ���� 2.6.x ��������� � �������� /usr/src/linux/
(http://www.linuxmafia.com/faq/Kernel/usr-src-linux-symlink.html) 

���� ���������� ����� ��������, ����������� ��������� �������.


��� ������� �������������� � ������ 2.6.[0123], � ����� ������������� ��� �� �������������
�� ������ ������.


#!/bin/sh
cp export_sys_call_table_patch_for_linux_2.6.x /usr/src/linux/
cd /usr/src/linux/
patch -p0 < export_sys_call_table_patch_for_linux_2.6.x
     


����� 8. ���������� ���������

��� �� �������, ����� ���-�� ������ ��� � ���-��, � �� ��
������ ������� ��� ����������? ������� ������������, ��� ��
������ ��������: «����������, �� ������, � ����
�����.». � ��� ������ ������ ������ ����? � ���� ���� ������
�����������. �� ������ ������������� ������ �������� �� ��� ���,
���� �� ������ ��������� ���. � �������� �����, ���� ��������� ��
����������������, �� ����� ������������ ������ ���������. ������
��� �������������� ����������� �������������� ����������
���������� ��������� �� ������������ ����������.

������ ���� ������������� ����� �����������. ������ �������
���� /proc/sleep, ������� ����� ����
������ ������ ����� ���������, � ������ ���������� ������
�������. ���� ���� ��� ��� ������ ���-������, �� ������ ��������
wait_event_interruptible. [10] ���
������� �������� ��������� «������» (�����, ���
�������� «������» ���������� ��������� ������ �
����, ������� ������ ���������� � ��������), ���������� ���
�������� TASK_INTERRUPTIBLE, ���
��������, ��� ������ �� ����� ����������� �� ��� ���, ���� ��
����� «���������» ����� ���� �������, � ���������
������� � ������� �������� WaitQ,
���� ���������� ��� ��������, �������� ������� ���� /proc/sleep. ����� ������� �������� ����������
������������, ������� � ���� ������� ������������� �����������
���������� ������� ��������.

����� ������� ��������� ����, ��� �������� � ������ module_close. ��� ��������� ��� ��������, �������
«�����» � ������� WaitQ (�
��������� ��� ���������, ������� �������� ��
«���������» ������ ���� �������). ����� ����������
������������ ��������, ������� ������ ��� ������ ���� � ��
���������� ���� ������. ����� ����, ��� ������ ������� ���������
���� ����� �������, ����������� �������� ���������� �������
��������. ����� �������, ���� �� ���������, ��������� �����
������� ������� � �����, � �������� ����� ������� ���������� �
��������� ���������� � �����, ��������� �� ������� wait_event_interruptible. [11] ��
��������� ���������� ����������, ���������� ��������� ��������
� ���, ��� ���� ������ � �������� ���������� ��������� �����.
����� ������ �������� ������� ���� ����� �������, ���
���������, ��� ���� ��� ��� ������ � ����� ������������ ����
������.

����� ���-�� ������� ������������� ������, ��� module_close �� �������� ����������� ������ ��
������������� ������ ��������� ���������. ������ Ctrl-C (SIGINT)
����� ����� «���������» �������. [12]
���� ������ �������� ���������� ������������ -EINTR. ����� ������� ������������ �����,
��������, �������� ������� ������, ��� �� ������� ������ �
�����.

��� ���� ��� ���� ������, � ������� �������� �� ���������.
��������� �������� �� ������ ���� ����������������, �����
�������� ������ ���� �������� � ���� ������������ �������� ����
����������, ���� ��������� � ���, ��� �� ������ �� ����� ����
������������ � ��������� ������. ����� �������� ���������� ����
O_NONBLOCK ��� �������� �����. ����
���� �� � ��������� ���������� ������������� ������, ��� ��������
����� ������ -EAGAIN.

������ 8-1. sleep.c

/*
 *  sleep.c - ������� ���� � /proc, ������ � �������� ����� �������� ������ ���� �������,
 *  ��� ��������� ����� ��������������.
 */

#include <linux/kernel.h>   /* ���-���� �� �������� � �����! */
#include <linux/module.h>   /* ���������� ��� ������ ������ */
#include <linux/proc_fs.h>  /* ���������� ��� ������ � /proc */
#include <linux/sched.h>    /* �������������� � ������������� */
#include <asm/uaccess.h>    /* ����������� ������� get_user � put_user */

/* 
 * ����� �������� ���������� ��������� ���������,
 * ������� ����� ���������� � ����, ����� ��������, ���
 * ������ ������������� ����� �������� ���� �� ������������
 */
#define MESSAGE_LENGTH 80
static char Message[MESSAGE_LENGTH];

static struct proc_dir_entry *Our_Proc_File;
#define PROC_ENTRY_FILENAME "sleep"

static ssize_t module_output(struct file *file, /* ��. include/linux/fs.h   */
                      char *buf,                /* ����� � ������� (� ������������ ������������) */
                      size_t len,               /* ������ ������ */
                      loff_t * offset)
{
  static int finished = 0;
  int i;
  char message[MESSAGE_LENGTH + 30];

  /* 
   * ��� ��������� �������� ����� ����� ������������ 0.
   * � ��������� ������ ������� ����� ���������� ������ �� �����
   * ������ � ����������� ����. 
   */
        if (finished) {
                finished = 0;
                return 0;
        }

  /* 
   * ��� �������� ������ �� ������������ ���� � ������������ ������������ 
   * ������� ������������ put_user.
   * � �������� ����������� -- get_user.
   */
  sprintf(message, "Last input:%sn", Message);
  for (i = 0; i < len && message[i]; i++)
    put_user(message[i], buf + i);

  finished = 1;
  return i;    /* ������� ���������� "�����������" ���� */
}

/* 
 * ��� ������� ��������� ��������� ������������� ���������
 */
static ssize_t module_input(struct file *file,  /* ���������� ���� */
          const char *buf,                      /* ����� � ���������� */
          size_t length,                        /* ������ ������ */
          loff_t * offset)
{         /* �������� � ����� - ������������ */
  int i;

  /* 
   * ����������� ������, ���������� �� ������������ � �����, 
   * ������� ������� ����� ������� �������� module_output.
   */
  for (i = 0; i < MESSAGE_LENGTH - 1 && i < length; i++)
    get_user(Message[i], buf + i);

  /* ������� ������, ������������� ��������  */       
  Message[i] = '';

  /* 
   * ������� ����� �������� ����
   */
  return i;
}

/* 
 * 1 -- ���� ���� ������
 */
int Already_Open = 0;

/* 
 * ������� ��������
 */
DECLARE_WAIT_QUEUE_HEAD(WaitQ);
/* 
 * ���������� ��� �������� ����� � /proc 
 */
static int module_open(struct inode *inode, struct file *file)
{
  /* 
   * ���� ���������� ���� O_NONBLOCK, �� ������� �� ������ ������������������
   * � ���� ������, ���� ���� ��� ������, ���������� ������� ��� ������
   * -EAGAIN, ��� �������� "���������� � ������ ���"
   */
  if ((file->f_flags & O_NONBLOCK) && Already_Open)
    return -EAGAIN;

  /* 
   * ��������� ������� ���������,
   * ����� ���������� ���� ��������� ������
   */
  try_module_get(THIS_MODULE);

  /* 
   * ���� ���� ��� ������ -- ������������� �������
   */

  while (Already_Open) {
    int i, is_sig = 0;

    /* 
     * ��� ������� ������������ ������� � �������� ��� � ������� ��������.
     * ���������� �������� ����� ���������� � �����, ��������� �� �������
     * ���� �������, ����� ��� ������ ������� ����� 
     * wake_up(&WaitQ) (��� �������� ������ ������ module_close, ����� 
     * ���� ����� ������) ��� ����� �������� �������� ������ Ctrl-C
     */
    wait_event_interruptible(WaitQ, !Already_Open);

    for (i = 0; i < _NSIG_WORDS && !is_sig; i++)
    is_sig =
          current->pending.signal.sig[i] & ~current->
          blocked.sig[i];

    if (is_sig) {
      /* 
       * �� ������ ������� ����� module_put(THIS_MODULE),
       * ��������� ������� ��� �������
       * � ������� �� ������� ������� close.
       * ���� �� ��������� ������� ���������, �� �� �������� ��������� 
       * ������ ����, � ���������� ������ ����� �����
       * ���������� ������ ��� ������������ �������
       */
      module_put(THIS_MODULE);
      return -EINTR;
    }
  }

  /* 
   * � ���� ����� ���������� Already_Open ������ ���� ����� ����
   */

  /* 
   * ������� ����
   */
  Already_Open = 1;
  return 0;
}

/* 
 * ���������� ��� �������� �����
 */
int module_close(struct inode *inode, struct file *file)
{
  /* 
   * �������� ���� � Already_Open, ����� ���� �� ��������� �� WaitQ
   * ������ �������� ���� ������� � ������� ����.
   * ��� ��������� ��������, ��������� ������� � ����� ����� ����� ��������������
   */
  Already_Open = 0;

  /* 
   * ����������� ������ ��������� �� WaitQ.
   */
  wake_up(&WaitQ);

  module_put(THIS_MODULE);

  return 0;
}

/* 
 * ��� ������� ��������� ������� � ����� �� ���������� �������� � ������
 * 0 -- ���������, �������� �������� -- ���������.
 *
 * �������� � ������ ����� ����:
 * 0 - ����������e (�� ����� ������ � ����� ��������)
 * 2 - ������ (�������� �� ������������ � ������ ����)
 * 4 - ������ (�������� �� ������ ���� � ������������)
 *
 * ��� ������� ��������� ����� ������� � �����
 * �����, ��������� �������� ls -l 
 * ����� ���� ��������������� �����.
 */
static int module_permission(struct inode *inode, int op, struct nameidata *nd)
{
  /* 
   * �������� ������ ������ ����, ��
   * ������ -- ������ root-� (uid 0)
   */
  if (op == 4 || (op == 2 && current->euid == 0))
    return 0;

  /* 
   * ���� ���-�� ���� -- ��������� ������
   */
  return -EACCES;
}

/* 
 * ��������� �� �������-����������� ��� ������ �����.
 */
static struct file_operations File_Ops_4_Our_Proc_File = {
  .read = module_output,    /* ������ �� ����� */
  .write = module_input,    /* ������ � ���� */
  .open = module_open,      /* �������� ����� */
  .release = module_close,  /* �������� ����� */
};


/* 
 * �������� ��� ��������� ������� ������ �����. ����������
 * ��� ����, ����� ������� �������������� ���������
 * file_operations ������ �����, � ��� ��, ����� ������
 * ������� ����������� ���� ������� � �����. ����� ����� ������� ������
 * ������ �������-������������, �� ��� ��� �� ����������.
 */
static struct inode_operations Inode_Ops_4_Our_Proc_File = {
  .permission = module_permission,      /* check for permissions */
};

/* 
 * ��������� � �������� ������� ������
 */

/* 
 * ������������� ������ - ����������� ����� � /proc
 */

int init_module()
{
  int rv = 0;
  Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL);
  Our_Proc_File->owner = THIS_MODULE;
  Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File;
  Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File;
  Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR;
  Our_Proc_File->uid = 0;
  Our_Proc_File->gid = 0;
  Our_Proc_File->size = 80;

  if (Our_Proc_File == NULL) {
    rv = -ENOMEM;
    remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
    printk(KERN_INFO "Error: Could not initialize /proc/testn");
  }

  return rv;
}

/* 
 * ���������� ������ ������ - ������������� ����� � /proc. ������� �������������
 * ���� � WaitQ �������� ��������, ��������� ����� �������, ��������� ����� �� ����������
 * ����������� ��������� � ������� open, ������� ����� ��������� ��� �������� ������. 
 * �������, � 9 �����, � ����� ��� ����������������� �������� ������ � ����� �������
 */
void cleanup_module()
{
  remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
}
     
     


����� 9. ������ printk

9.1. ������
printk

����� � ��� ������� � ���, ��� XWindow � ���������� �������
���� ���� ���� �������������. ��� ��� ���, �� ������ ���������
������������� ������ ��������� �� ������ �� ����� tty. [13]

� �������� ������ �� ��������� ����� ���������� ���������:
��������� ��������� �� ������� ����������� ������� —
current, �������� ���������
tty. ����� ������� �� ���� ���������
��������� �� ������� ������ ������ � ������������ �� ��� ������
���������.

������ 9-1. print_string.c

/* 
 *  print_string.c - ���������� ����� �� tty ���������, ���������� �� ���� 
 *  X11 ���, ��� telnet, ��� ���-�� ���. �������� ��� ����� ������ ������ �� tty,
 *  ��������������� � ������� ���������.
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>   /* ����������� current */
#include <linux/tty.h>     /* ����������� tty */
#include <linux/version.h> /* ������ LINUX_VERSION_CODE */

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter Jay Salzman");

static void print_string(char *str)
{
  struct tty_struct *my_tty;

  /* 
   * ������� � ������ 2.6.6, ��������� tty ������������ � ��������� signal 
   */
#if ( LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,5) )
  /* 
   * tty �������� ��������
   */
  my_tty = current->tty;
#else
  /* 
   * tty �������� ��������, ��� ���� 2.6.6 � ����
   */
  my_tty = current->signal->tty;
#endif

  /* 
   * ���� my_tty == NULL, �� ������� ������� �� ����� tty �� ������� ����� 
   * ���� �� ��� ������ ������� (��������, �����).  
   * � ���� ������ ��� ������ �� ����� ������.
   */
  if (my_tty != NULL) {

    /* 
     * my_tty->driver -- ���������, ������� ������ ��������� �� �������-�����������,
     * ���� �� ������� (write) ������������ ��� ������ ����� �� tty. 
     *
     * ������ �������� ������� -- tty, �� ������� �������������� �����,
     * ��������� ��� ������� ������ ������������ ��� ������ �� ��� 
     * tty ������ � ���� �� ����. 
     * ������ �������� -- ���� ������������ ������
     * ���� ������ ��������� � ������������ ����, ���� ����� false (0)
     * ���� � ������������ ������������, �� true (�� ����).  
     * ������ �������� -- ��������� �� ������.
     * ��������� �������� -- ����� ������.
     */
    ((my_tty->driver)->write) (my_tty,  /* ���������� tty  */
             0,                         /* ������ � ������������ ���� */
             str,                       /* ���� ������ */
             strlen(str));              /* ����� ������ */

    /* 
     * tty ���������� ��� ���������� �����������, ������� (������) 
     * ������������� ���������� ASCII, � ������� ������� ������
     * ������� � ���� ��� ������� -- "������� �������" � "������� ������".
     * � Unix, ������ ASCII -- "������� ������" ������� ��� ���� �������,
     * ������� ��� �������� ������������ ��� �������� ������
     * ��� �������.
     *
     * ��� ���� �� ������ �������� ����� ���������� ������� Unix �
     * MS Windows. CP/M � �� "����������", �������� MS-DOS �
     * MS Windows, ������ �������������� ��������� ASCII.
     */
    ((my_tty->driver)->write) (my_tty, 0, "1512", 2);
  }
}

static int __init print_string_init(void)
{
  print_string("The module has been inserted.  Hello world!");
  return 0;
}

static void __exit print_string_exit(void)
{
  print_string("The module has been removed.  Farewell world!");
}

module_init(print_string_init);
module_exit(print_string_exit);
     
     


9.2.
���������� ������������ �� ����������

��� ������������ �������� � ��� ����� ���������� �������
���� ������ ������ ����� ������� � ����� ������������� ������
�������������� � ������� �����. ��������� ���������
������������ ����������� ���������� ����� ���� ����� ��
��������� ����������� �������� ������������ ��� �����������
���������� ���������. ������������ ���������� ������������ ��
����� ����������, ��� ������ ��������� � ���� ������, ��� ��
��������� � ���������, � �� «�������������»
���������� �����������, �� ��������� � ������� �� tty ��� �
����.

��������� �������� ��� ������������ ������ ����, �������
����� �������� �������� ������ ������������ ����������.

������ 9-2. kbleds.c

/* 
 *  kbleds.c - ������� ������������ �� ����������.
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/tty.h>    /* ����������� fg_console, MAX_NR_CONSOLES */
#include <linux/kd.h>     /* ����������� KDSETLED */
#include <linux/console_struct.h> /* �����������  vc_cons */

MODULE_DESCRIPTION("������ module illustrating the use of Keyboard LEDs.");
MODULE_AUTHOR("Daniele Paolo Scarpazza");
MODULE_LICENSE("GPL");

struct timer_list my_timer;
struct tty_driver *my_driver;
char kbledstatus = 0;

#define BLINK_DELAY   HZ/5
#define ALL_LEDS_ON   0x07
#define RESTORE_LEDS  0xFF

/*
 * ������� my_timer_func ������ ������������ �� ���������� ������������ ������� 
 * ioctl() �������� ���������� � �������� KDSETLED. �������������� ����������,
 * �� �������� ioctl ������������ ���������, �� ������� �:
 *     /usr/src/linux/drivers/char/vt_ioctl.c, function vt_ioctl().
 *
 * �������������� �������� ������� KDSETLED -- �������� 7 (������� � �����
 * LED_SHOW_IOCTL -- ���������� ������������ ����� ioctl), �������� 0xFF --
 * (����� ��������, ������� 7, ������� � ����� LED_SHOW_FLAGS --
 * ����������� ������������ ��������� ����������). �������������� ����������:
 *     /usr/src/linux/drivers/char/keyboard.c, function setledstate().
 * 
 */

static void my_timer_func(unsigned long ptr)
{
  int *pstatus = (int *)ptr;

  if (*pstatus == ALL_LEDS_ON)
    *pstatus = RESTORE_LEDS;
  else
    *pstatus = ALL_LEDS_ON;

  (my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
          *pstatus);

  my_timer.expires = jiffies + BLINK_DELAY;
  add_timer(&my_timer);
}

static int __init kbleds_init(void)
{
  int i;

  printk(KERN_INFO "kbleds: loadingn");
  printk(KERN_INFO "kbleds: fgconsole is %xn", fg_console);
  for (i = 0; i < MAX_NR_CONSOLES; i++) {
    if (!vc_cons[i].d)
      break;
    printk(KERN_INFO "poet_atkm: console[%i/%i] #%i, tty %lxn", i,
           MAX_NR_CONSOLES, vc_cons[i].d->vc_num,
           (unsigned long)vc_cons[i].d->vc_tty);
  }
  printk(KERN_INFO "kbleds: finished scanning consolesn");

  my_driver = vc_cons[fg_console].d->vc_tty->driver;
  printk(KERN_INFO "kbleds: tty driver magic %xn", my_driver->magic);

  /*
   * ������������ ������
   */
  init_timer(&my_timer);
  my_timer.function = my_timer_func;
  my_timer.data = (unsigned long)&kbledstatus;
  my_timer.expires = jiffies + BLINK_DELAY;
  add_timer(&my_timer);

  return 0;
}

static void __exit kbleds_cleanup(void)
{
  printk(KERN_INFO "kbleds: unloading...n");
  del_timer(&my_timer);
  (my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
          RESTORE_LEDS);
}

module_init(kbleds_init);
module_exit(kbleds_cleanup);
      
      
      

���� �� ���� �� �������� ������ ����� ��� �� ����������,
����� ����������� ������ ��������, ������� � ����. ����� ����
��� �������� ����� CONFIG_LL_DEBUG � make menuconfig? ������� ��, �� ��������
�������������� ������ � ����������������� �����. ��� �� �������
��� �� ����������, �� ������ ����������� �������� ����������
kernel/printk.c ��� ������ ������
������� ���������� ������ ��� ������ ascii-�����, ����� �����
����������� ����������� �������� ������ ������ �����
���������������� ����� �����.

�������� �� ��, ��� �� ��� ��������� � ���� ����� ������
��������� ������� �������, ���������� ��� ��� ��������, �������
��� ���������� �����. ������� — ��� ������ ����� ������������
������� � ����������� ������ �� �������������� ����������
������������� ���������� ����������� ����. ����� ��������� ���,
��� ���������� ��� �� ���� ����������� ��������� �������.
�������, ��� ������� ������ ������, ���������� ������
���������� ��� � �������� � «��������» ������ ���
���, ������� ���������� ����� ���� ������.


����� 10. ������������ �����

����� ����� ��������� ������������� ������� ���������������
����� �� ����������. ���� ����������� ������ — ������� �������,
�� �������� �� � ���� crontab. ���� �� ������ �������� �������
����, �� � ��� ���� ��� �����������. ������ ������� � ���, �����
��������� ����� ������ � ���� crontab, ������� �����
«������» ������ ��������� ������� � �������� ������
�������, ��������, �������� ����. ��� ����� ������������, �.�.
��� ������� ������ �������� �� crontab ���������� ���������
��������� � ������ � ���� ��� ������ ��� ����, �����
«���������» ������ ����, ������� ��� ��������� �
������.

������ ����� �� ��������� ������� �������, ������� �����
���������� �� ����������� �� �������. ��� �����, �������� ������
struct work_struct. ��� ��������� �����
������� ��������� �� �������, ������������� �� �������. �����, �
������� queue_delayed_work, ��������
������ � ������� tq_timer, ��� ������
������������� ������, ������������� �� �������. � ��� ���
�������������� ������������ ������� ������ ���, �� ���������
��������� ��������� �������, �� ������ ������ ��� ����� ���������
�� � ������� tq_timer.

� ��� ���� ������������ ������. ����� ������ �����������
�������� rmmod, �������
����������� ������� ��������� � ������. ���� �� ����� ����, ��
���������� module_cleanup. ����� ����
������ ��������� �� ������ �� ����� ��� ���������. � ����� ��
��������� — �������� �� ������� ����� ������� ��������� �� ����
�� ��������� �������. �� ���������� ���������� ������� (� �����
������ �������� — ����������� ���������), ���� �������
���������� �� ������� � ��������� �������� ��������� �� �������
������. �� �������-�� ������ ���! � ����������� ������� ��������
������, ��� ��� ����, ����� ��������������� ��� �������������� �
�� �������� ��������� �� ������. �� ����� ��������� ���, ��� ��
���� ����� �������� ��������� ������ ��� � ����� ���� ���� —
�����. � ���������, � ��� ��� ���������� �������� �������
�������� ������ �� ������� �������.

��� ��� cleanup_module �� �����
������� ��� ������ (��� �� ����� ������������� ��������), ��
������������� ������� — ������������� ��������� ����������
������ ������. ������ ����, ����� ���������� ��������� ������
������� cleanup_module, �� �����
������������� ������ ������� rmmod. �����, ��������� ���������� ����������,
�������� �������, ���������� �� ���������� �������, ����� ���
������ ���� �� ������� (������ — ����� ��� ����� �� ���������
���� � �������). �� ��������� ���������� �������, �������
rmmod �����
«��������», ����� ������� ������ ���� �� �������
������� � �������� ������ ������ ����������.

������ 10-1. sched.c

/*
 *  sched.c - ���������� ������������ �� �������.
 *
 *  Copyright (C) 2001 by Peter Jay Salzman
 */

/* 
 * ����������� ������������ �����
 */

/* 
 * �������, ��� ������� ����
 */
#include <linux/kernel.h>    /* ���-���� �� �������� � �����! */
#include <linux/module.h>    /* ���������� ��� ������ ������ */
#include <linux/proc_fs.h>   /* ���������� ��� ������ � /proc */
#include <linux/workqueue.h> /* ������� ����� */
#include <linux/sched.h>     /* �������������� � ������������� */
#include <linux/init.h>      /* �������  __init �  __exit */
#include <linux/interrupt.h> /* ����������� irqreturn_t */

struct proc_dir_entry *Our_Proc_File;
#define PROC_ENTRY_FILENAME "sched"
#define MY_WORK_QUEUE_NAME "WQsched.c"

/* 
 * ������� ������������ �� �������
 */
static int TimerIntrpt = 0;

static void intrpt_routine(void *);

static int die = 0;  /* 1 -- ��������� ������ */

/* 
 * ������� �����, ��������� ��� ����, ����� ��������� � ������� ������� (workqueue.h)
 */
static struct workqueue_struct *my_workqueue;

static struct work_struct Task;
static DECLARE_WORK(Task, intrpt_routine, NULL);

/* 
 * �������-���������� ���������� �� �������. �������� �������� �� �������� ���� void*
 * ������� ����� �������� �������������� ��������� ����������� ����� ���������.
 */
static void intrpt_routine(void *irrelevant)
{
  /* 
   * ��������� �������
   */
  TimerIntrpt++;

  /* 
   * ���� ������� ���������� �������, �� ����� �������� ���� � ������� �������
   */
  if (die == 0)
    queue_delayed_work(my_workqueue, &Task, 100);
}

/* 
 * ������ ������ � ���� /proc.
 */
ssize_t
procfile_read(char *buffer,
        char **buffer_location,
        off_t offset, int buffer_length, int *eof, void *data)
{
  int len;              /* ����������� ����� ���������� ���� */

  /* 
   * ���������� ��������� ��� static, ������� ��� ������������� �� �� �����
   * �������, � � ������ ������
   */
  static char my_buffer[80];

  static int count = 1;

  /* 
   * ��� �������� �������� �� ���� �������, �������, ���� �������� != 0, �� ������
   * ��� ������ ������ �������, ������� ������������ 0, � �������� �������� ����� �����.
   */
  if (offset > 0)
    return 0;

  /* 
   * ��������� ����� � �������� ��� �����
   */
  len = sprintf(my_buffer, "Timer called %d times so farn", TimerIntrpt);
  count++;

  /* 
   * ������� ����� ������
   */
  *buffer_location = my_buffer;

  /* 
   * ������� ����� ������
   */
  return len;
}

/* 
 * ������� ������������� - ���������������� ���� � /proc
 */
int __init init_module()
{
  int rv = 0;
  /* 
   * ������� ������� ����� � ����� ������� � ��������� �� � ������� �������
   */
  my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
  queue_delayed_work(my_workqueue, &Task, 100);

  Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL);
  Our_Proc_File->read_proc = procfile_read;
  Our_Proc_File->owner = THIS_MODULE;
  Our_Proc_File->mode = S_IFREG | S_IRUGO;
  Our_Proc_File->uid = 0;
  Our_Proc_File->gid = 0;
  Our_Proc_File->size = 80;

  if (Our_Proc_File == NULL) {
    rv = -ENOMEM;
    remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
    printk(KERN_INFO "Error: Could not initialize /proc/%sn",
           PROC_ENTRY_FILENAME);
  }

  return rv;
}

/* 
 * ���������� ������
 */
void __exit cleanup_module()
{
  /* 
   * ������� ���� �� /proc
   */
  remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
  printk(KERN_INFO "/proc/%s removedn", PROC_ENTRY_FILENAME);

  die = 1;                    /* ��������� ������� ��������� ���������� � ���������� ������ */
  cancel_delayed_work(&Task); 
  flush_workqueue(my_workqueue); /* ����� ���� ���������� ������ */
  destroy_workqueue(my_workqueue);

  /* 
   * ������������� ������, ���� intrpt_routine �� ���������� � ��������� ���. 
   * ��� ����������, ��������� �� ����������� ������, ���������� ���� ��������.
   */

}

/* 
 * ��������� �������, ����������� � work_queue 
 * �������� ������ ���� ������ ������������ ��� GPL
 */
MODULE_LICENSE("GPL");
     
      
      


����� 11. ��������� ����������

11.1.
��������� ����������

�����, ����� ���������� �����, ��� ���� �������� � ����
��������� � ������� �� ������ ������� �� ���������, � ������ ��
������������ ������, ������� ������ ioctl ��� ������� ���������
�������. ������ ������ ���� �� ����� �������� ������ �
��������� ��������. ��� ���� ������������ ������ — ��� ������
� ����������� ����������.

���������� ��� ���� �������������� ����� CPU � ���������
���������� ������ ����������. ������ — �������� ������
���������� ���������, ������ — ����� ������� �� ����������.
������ ��� �������������� — ����������, �������� ��������
������� � ���������, ������ ��� ���������� ��������� �����,
����� ��� ������ ����������, � �� CPU. ���������� ����������
������ ����� ������ ������������ ����� ���, � ���� �� �������
������������ ��� ���������� ����������, �� ��� �����
����������.

� Linux ���������� ���������� ���������� IRQ (���������� �� Interrupt ReQuests —
������� �� ����������). [14]
������� ��� ���� IRQ: «��������» �
«�������». «��������» IRQ �������� �����
�������� ������ �������, � ������� �������� ������
������������ ������� ����� �������������, � ��� �� �����
���������� ��������� ������ ����������. «�������»
IRQ ����� ������ �������� ��������������� �����, � �������
�������� ����� �������������� � ������ ���������� (�� ��
���������� �� ���� �� ������ ����������). �������, ������
������ ������������� ������� ���������� ������ ��
����������� ������ ����������� ���������� (�.�.
������������� ����������, ��������� ��������� � ��.) �
������, ������� ����� ���� �������� �� ��������� �����
(�������� ������������� ������, ����������� ���������,
��������� ��� ������ � �.�.). ���� ��� ��������, �����
��������� ����������� ���������� «��������».

����� CPU �������� ����������, �� ������������� �����
�������� (���� ��� �� ����� ������������ ����������, �����
��������� ���������� ���������� ���������� ������ �����, �����
����� ������������ ����� ���������), ��������� ���������
��������� � ����� � �������� ���������� ����������. ���
��������, ��� �� ��� �������� ��������� ������ �����������
����������, ������ ��� ������� ��������� � �����������
���������. ������� ��������: ���������� ���������� ����������
— ��� ������ ���� ������� ���������� (������ ���-�� ���������
�� ���������� ��� ���-�� ������� ���), � ����� �������������
��������� ����������� ���������� �� ����� ������� ����� (���
���������� «bottom halves» — «������
��������») � ������� ����������. ���� ����������� �����
«������ ��������» ��� ������, ��������� ��� ��������.
����� ��� ����������, �� ��� ���������� — «������
��������», ��� �� ����� ������� ������-�� ������� � ���
����� �������� ��� ��, ��� �������� ������� ������� ����.

��������������� ���������� ���������� ������� request_irq. �� ���������� ����� IRQ, ���
�������-�����������, �����, ��� ��� /proc/interrupts � �������������� �������� ���
����������� ����������. ����� ����� �������� SA_SHIRQ, ����� �������, ��� ���������� �����
������������� ����������� ������������� (������, �� ��� �������
�������, ��� �� ����� IRQ ����� «������» ���������
���������) � SA_INTERRUPT, �����
�������, ��� ��� «��������» ����������. ��� �������
��������� ���������� ������ � ��� ������, ���� �� �������� IRQ
��� ��� ����������� ����������, ��� ���� ������������
���������� ��������������� ���������� ��������� ����������
������ SA_SHIRQ.

�� ����� ��������� ����������, �� �������-�����������
����������, �� ����� �������� ������ �� ���������� � �����, �
������� queue_task_irq, tq_immediate � mark_bh(BH_IMMEDIATE), �������������
«������ ��������». � ������ ������� Linux ������
������ ������ �� 32 «������ �������», ������ ��, ����
�� ��� (� ������ BH_IMMEDIATE) ������������ ��� ������������
������ ������ «������ �������» ���������. �����
mark_bh(BH_IMMEDIATE) ��� ��� �
��������� «������ ��������» �������� � ���� ������,
�������� ����� ������� �� ����������.


11.2. ���������� ��
����������� Intel

��������, ��������������� � ���������� ����� ���� �����,
����� ���� �������� ������������� � ����������� Intel. ��
������ ���������� ��� ������� �������� �� �����.

���� ����� ������ ������� ��� ��������, ������� ����� ����
�� ������������ � �������� ������� � ���� �����. � �����
������� ������ ������ ���� ���������� ��������, �� ������
�������� �� ����� ���������� � ���� ���������� �������������. �
������ �������, � ���� ��� �������� �������� ����������
��������� ����������� ��� ���� ������������� �
���������������������� ���������. ��� �������� �� ������ ��
��������� �������� � ���, ��� � ��������� ��������. ������� �
������ ������� ����������� � �������� ������� — ����������
���������� �� ����������, �� ��� ������������ �����������������
���� ������� �������� ��������� ����������� ����������
���������� �� ����������, � ��� ��� ���� ������ �������� ���
static (� ����� drivers/char/keyboard.c), �� ��� ��������
������� ������������ ����������. �������, ������ ��� �� ������
������� insmod, ��������� �
������ ������� � ����� ������� sleep
120; reboot
, ���� ���� �������� ������� ������������
��� ��� ���� �����-������ ��������.

���� ������ ����������� ��������� IRQ 1 — ���������� ��
���������� �� ����������� Intel. ��� ��������� ����������
���������� ������ ��������� ���������� (inb(0x64)) � ����-���
������� �������. �����, ��� ������ ���� ������ ���������, ���
�������� got_char (��� ������ ����
«������ ��������»), ������� �������, ����� printk, ��� ������� (������� ���� ���
����-����) � ������� «������/��������» (8-� ���
����-���� — 0 ��� 1 ��������������).

������ 11-1. intrpt.c

/*
 *  intrpt.c - ���������� ����������.
 *
 *  Copyright (C) 2001 by Peter Jay Salzman
 */


/* 
 * Standard in kernel modules 
 */
#include <linux/kernel.h>    /* ���-���� �� �������� � �����! */
#include <linux/module.h>    /* ���������� ��� ������ ������ */
#include <linux/workqueue.h> /* ������� ����� */
#include <linux/sched.h>     /* �������������� � ������������� */
#include <linux/interrupt.h> /* ����������� irqreturn_t */
#include <asm/io.h>

#define MY_WORK_QUEUE_NAME "WQsched.c"

static struct workqueue_struct *my_workqueue;

/* 
 * ��� ������� ���������� �����, ������� � ��� ����� ��������� ��� ��������
 * ������� ��������� � ������� ����.
 */
static void got_char(void *scancode)
{
  printk("Scan Code %x %s.n",
        (int)*((char *)scancode) & 0x7F,
        *((char *)scancode) & 0x80 ? "Released" : "Pressed");
}

/* 
 * ���������� ���������� �� ����������. �� ��������� ���������� � ����������
 * � �������� �� ����� ��������� �� ������� ���������� �����,
 * ������� ����� �������� ����� ��, ��� ������ ���� ������ ��� ���������.
 */
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
  /* 
   * ��� ���������� ��������� ������������, ����� ������� �����������
   * ������� � ��� (����������� ����������) �� "������ ��������".
   */
  static int initialised = 0;
  static unsigned char scancode;
  static struct work_struct task;
  unsigned char status;

  /* 
   * ��������� ��������� ����������
   */
  status = inb(0x64);
  scancode = inb(0x60);

  if (initialised == 0) {
    INIT_WORK(&task, got_char, &scancode);
    initialised = 1;
  } else {
    PREPARE_WORK(&task, got_char, &scancode);
  }

  queue_work(my_workqueue, &task);

  return IRQ_HANDLED;
}

/* 
 * ������������� ������ - ����������� ����������� ����������
 */
int init_module()
{
  my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);

  /* 
   * ��������� ����������� ���������� ���������� �� ���������� �� �����
   * �������������� � ����� ��� ���, �� �������� ��������� ���
   * (���������� IRQ) ������, ��� ��� ���� �������.
   * �� ��������� �� �� ����� ��� �� ��������� � ����, �� �� ������
   * ����������� �������������� ��� - ������� ��������� �������� �������������
   * ����� ����������� ����� �������.
   */
        free_irq(1, NULL);

  /* 
   * ���������� ���� ���������� (irq_handler) �� IRQ 1.
   * SA_SHIRQ ��������, ��� �� ��������� ����������� �����������
   * ������������ ����� IRQ ������� �������������.
   */
  return request_irq(1,    /* ����� IRQ */
         irq_handler,      /* ��� ���������� */
         SA_SHIRQ,
         "test_keyboard_irq_handler",
         (void *)(irq_handler));
}

/* 
 * ���������� ������
 */
void cleanup_module()
{
  /* 
   * ��� ������� ��������� ���� ��� ������� ���������. 
   * ��� ������ ������������, ��������� � �� ���� �������
   * ������������ ����������� ���������� ���������� �� ���������� 
   * ������� ���������� ��������� ������������ �������.
   */
  free_irq(1, NULL);
}

/* 
 * ��������� �������, ����������� � work_queue 
 * �������� ������ ���� ������ ������������ ��� GPL
 */
MODULE_LICENSE("GPL");
       


����� 12. ������������
�������������������

���� �� ����� ������� � ����� ������� �������� ����������
������������������ — ��� ���������� �� ����������� �����
��������� ����������������. ������ �� ����������� ����� ������
���� ����������� ���� (������������� ����������������� ���������
— ASMP) ��� �� ��� ��� �������� �����������, ���������
���������� ����� �������, ��� ������� � ������������ �������, �
���������� ����� ������������ ����� ��������� ���������
(������������ ����������������� ��������� — SMP). ��������
������������� ����������������� �� ����� ������� NetWare SFT III,
������������ ���������� ����������� ��������, � ����������������
������� ������ ��������� ���������� ��������������� �����, �
������ — ���������� �����/������. ������������� �����������������
��������� ����� ����������, �� ��� ������� ������� �������������
����� ����� ������������, ��� ����������� ���������� ��
������������� ��, �������� Linux. � ������ �������, ������������
����������������� ��������� ����� ����������, �� ������������
������ � ���������� (�����, ��� ������� «������������
������», ����� �� ���������������, ��� ��� �������������
������).

� ������������ ����������������� ����� ���������������
��������� ���������� ���� � �� �� ������, � ���������� ���,
���������� �� ����� CPU ����� �������� ���������� ������,
������������ ������ CPU. ����� ��� ��� �����������, ���
����������, ������� �� ��������� ��������� ��������, � ����������
������ ���������, ��� ��� ����� �� �� ����� �������� — ���,
����������� �� ������ CPU ����� ��� ��������. ��������, ��� �
������ ����������� ������.

�� ����� ����, � ������ ������� ���������, ����� �������� ��
����������. ��� �������, � ������ ���������� ������ �������,
������� ����� ����������� ������ �� ����� CPU. [15] ��
���� ����� ��������� ������������ ������ �������� �� ������
CPU.

� ����� ������ 2.0.x ��� �� ���� ������� ���������, ���������
���� �������� ��� ������� ����� ������� ����-����������
(spinlock). ��� ��������, ��� ����� ���� ��������� �������� �
����������������� ������, � ������ ���������� ����� � ���� �����
(�������� � ������ ���������� ������), �� �� �������� �����, ����
������ ��������� �� ������ � ���������������� �����. ��� ������
SMP � Linux ����������, �� ���������������.

������� � ���� ������ 2.2.x ���� ��������� ������������� �����
���������� ����������� � ����������������� �����. ���
�������������� �� ������ ����� � �������.


����� 13. ����������

� ���������� � ����� �� ������������ ��� � ��������� �����
�������, ����������� �������������� ������� ����. ���� � ���-��
������� �� ����, � ��-�� ����� � ��� ��������� ����� ������� —
�������� ��� � � ����� ��� �� ���� ���������� ��������, ������� �
������� �� ��� ��������� �����.

����������� ����������. �� ��
������ ������������ ������� �� ����������� ��������� ����� C.
����������� ������ �� �������, ������� ��������������� �����
(����������� �� ��� �� ������� � /proc/kallsyms).

������ ����������. ���� ��
��������� ���������� �� �������� ����, �� ������ ��������� ��
����������. �� ���� �� �������� �� ���������, �� �������
«��������» � ������������� �� ����� ����� ������
������� ���������� �������.

�� ����� ������ � �����
�����
! �������� ��� �������������� �������, �� ��� ��
�����…

Перед вами последняя версия пособия по программированию модулей ядра Linux, вышедшего 2 июля 2022 года. Пособие большое, поэтому материал будет разбит на серию статей. В первой части мы разберём, что такое модули ядра, рассмотрим необходимые подготовительные этапы для их создания и в завершении по традиции напишем первый простейший модуль «Hello world», попутно разобрав лицензирование, передачу аргументов командной строки и прочие нюансы. Это пособие вы можете смело воспроизводить и изменять в соответствии с условиями Open Software License v 3.0.

1. Вступление

Эта книга задумана для распространения в качестве полезного подручного материала, но не предоставляет никаких гарантий, в том числе гарантий соответствия ожиданиям читателя или пригодности для конкретной цели.

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

Производные работы и переводы этого документа должны также публиковаться под лицензией Open Software License с упоминанием оригинального источника. Если вы внесёте в книгу новый материал, то сделайте этот материал и исходный код доступными для своих ревизий. Ревизии и обновления должны быть доступны непосредственно мейнтейнеру документа, Джиму Хуангу <jserv@ccns.ncku.edu.tw>. Это позволит делать мерджи обновлений и предоставлять согласованные ревизии сообществу Linux.

Если вы будете публиковать или распространять книгу в коммерческих целях, то автор и проект документирования Linux (LDP) будут весьма признательны за пожертвования, ройялти-отчисления и предоставление печатных версий. Участие в общем деле таким образом показывает вашу поддержку бесплатного ПО и LDP. По всем вопросам можете писать на приведённый выше адрес.

▍ 1.1 Авторство

Первое «Руководство по программированию модулей ядра» написал Ори Померанц для ядер версии 2.2. В конечном итоге у Ори не стало времени для поддержания актуальности этого документа, что не удивительно, ведь ядро очень динамично в своём развитии. После этого поддержку руководства взял на себя Питер Джей Зальцман, который обновил его под версию 2.4. Но в итоге и Питеру стало нехватать времени, чтобы довести пособие до соответствия ядру 2.6. В этой ситуации на выручку пришёл Майкл Буриан, который помог его обновить. Следующим был Боб Моттрам, доработавший примеры под ядро 3.8+. Последним же на данный момент является Джим Хуанг, который довёл руководство до соответствия последним версиям ядра 5.х и скорректировал документ LaTeX.

▍ 1.2 Благодарности

Ниже перечислены сторонние участники, которые вносили изменения и давали полезные рекомендации:

2011eric, 25077667, Arush Sharma, asas1asas200, Benno Bielmeier, Bob Lee, Brad Baker, ccs100203, Chih-Yu Chen, Ching-Hua (Vivian) Lin, ChinYikMing, Cyril Brulebois, Daniele Paolo Scarpazza, David Porter, demonsome, Dimo Velev, Ekang Monyet, fennecJ, Francois Audeon, gagachang, Gilad Reti, Horst Schirmeier, Hsin-Hsiang Peng, Ignacio Martin, JianXing Wu, linD026, lyctw, manbing, Marconi Jiang, mengxinayan, RinHizakura, Roman Lakeev, Stacy Prowell, Steven Lung, Tristan Lelong, Tucker Polomik, VxTeemo, Wei-Lun Tsai, xatier, Ylowy.

▍ 1.3 Что такое модуль ядра?

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

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

▍ 1.4 Пакеты модулей ядра

В дистрибутивах Linux для работы с пакетами модулей есть команды modprobe, insmod и depmod.

В Ubuntu/Debian:

sudo apt-get install build-essential kmod

В Arch Linux:

sudo pacman -S gcc kmod

▍ 1.5 Какие модули содержатся в моём ядре?

Узнать, какие модули загружены в ядро, можно командой lsmod:

sudo lsmod

Хранятся модули по пути /proc/modules, значит, их также можно просмотреть с помощью:

sudo cat /proc/modules

Список может оказаться длинным, и вам потребуется искать что-то конкретное. Вот пример поиска модуля fat:

sudo lsmod | grep fat

▍ 1.6 Нужно ли скачивать и компилировать ядро?

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

▍ 1.7 Перед началом

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

  1. Версионирование модулей. Модуль, скомпилированный для одного ядра, не загрузится для другого, если не включить в этом ядре CONFIG_MODVERSIONS. Подробнее о версионировании мы ещё поговорим позднее. Если в вашем ядре версионирование включено, то поначалу, пока мы не разберём эту тему подробнее, примеры могут у вас не работать. Правда, включено оно обычно в большинстве базовых дистрибутивов, и если из-за этого у вас возникнут проблемы с загрузкой модулей, скомпилируйте ядро, отключив их версионирование.
  2. Использование X Window System. Настоятельно рекомендуем извлекать, компилировать и загружать все приводимые в руководстве примеры из консоли. Работать с ними в X Window System не стоит.

Модули не могут выводить информацию на экран подобно printf(). При этом они могут логировать информацию и предупреждения, которые в итоге на экран выводятся, но только в консоли. Если вы вставите модуль (insmod) из xterm, то информация и предупреждения залогируются, но только в системный журнал. То есть увидеть вы все эти данные сможете лишь через journalctl. Подробности описаны в разделе 4. Для получения прямого доступа ко всей этой информации, выполняйте все действия в консоли.

2. Заголовочные файлы

Прежде чем вы сможете что-либо создавать, вам нужно установить для ядра заголовочные файлы. Для начала выполните:

в Ubuntu/Debian:

sudo apt-get update
apt-cache search linux-headers-`uname -r`

в Arch Linux:

sudo pacman -S linux-headers

Так вы узнаете, какие заголовочные файлы ядра доступны. Затем можно выполнить, например:

sudo apt-get install kmod linux-headers-5.4.0-80-generic

3. Примеры

Все примеры этого документа доступны в подкаталоге examples.

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

4. Hello World

▍ 4.1 Простейший модуль

Большинство людей, изучающих программирование, начинают с какого-нибудь примера «Hello world». Не знаю, что бывает с теми, кто от этой традиции отходит, но, думаю, лучше и не знать. Мы начнём с серии программ «Hello world», которые продемонстрируют различные основы написания модуля ядра.

Ниже описан простейший пример модуля.

Создайте тестовый каталог:

mkdir -p ~/develop/kernel/hello-1
cd ~/develop/kernel/hello-1

Вставьте следующий код в редактор и сохраните как hello-1.c:

/*
 * hello-1.c – простейший модуль ядра.
 */
#include <linux/kernel.h> /* необходим для pr_info() */
#include <linux/module.h> /* необходим для всех модулей */
 
int init_module(void)
{
    pr_info("Hello world 1.n");
 
    /* Если вернётся не 0, значит, init_module провалилась; модули загрузить не получится. */
    return 0;
}
 
void cleanup_module(void)
{
    pr_info("Goodbye world 1.n");
}
 
MODULE_LICENSE("GPL");

Теперь вам потребуется Makefile. Если вы будете копировать следующий код, то сделайте отступы табами, не пробелами.

obj-m += hello-1.o
 
PWD := $(CURDIR)
 
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
 
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

В Makefile инструкция $(CURDIR) может быть установлена на абсолютный путь текущего рабочего каталога (затем идёт обработка всех опций , если таковые присутствуют). Подробнее о CURDIR читайте в мануале GNU make.

В завершении просто выполните make.

make

Если в Makefile не будет инструкции PWD := $(CURDIR), он может не скомпилироваться корректно с помощью sudo make. Поскольку некоторые переменные среды регулируются политикой безопасности, наследоваться они не могут. По умолчанию эта политика определяется файлом sudoers. В нём изначально включена опция env_reset, которая запрещает переменные среды. В частности, переменные PATH из пользовательской среды не сохраняются, а устанавливаются на значения по умолчанию (подробнее можно почитать в мануале по sudoers).

Установки для переменных среды можно посмотреть так:

$ sudo -s
# sudo -V

Вот пример простого Makefile, демонстрирующий описанную выше проблему:

all:
    echo $(PWD)

Далее можно использовать флаг –p для вывода всех значений переменных среды из Makefile.

$ make -p | grep PWD
PWD = /home/ubuntu/temp
OLDPWD = /home/ubuntu
    echo $(PWD)

Переменная PWD при выполнении sudo унаследована не будет.

$ sudo make -p | grep PWD
    echo $(PWD)

Тем не менее эту проблему можно решить тремя способами.

1. Использовать флаг -E для их временного сохранения.

$ sudo -E make -p | grep PWD
    PWD = /home/ubuntu/temp
    OLDPWD = /home/ubuntu
    echo $(PWD)

2. Отключить env_reset, отредактировав /etc/sudoers из-под рут-пользователя с помощью visudo.

## файл sudoers.
  ##
  ...
  Defaults env_reset
  ## В предыдущей строке измените env_reset на !env_reset, чтобы сохранить все переменные среды.

Затем выполните env и sudo env по отдельности:

# отключить env_reset
    echo "user:" > non-env_reset.log; env >> non-env_reset.log
    echo "root:" >> non-env_reset.log; sudo env >> non-env_reset.log
    # включить env_reset
    echo "user:" > env_reset.log; env >> env_reset.log
    echo "root:" >> env_reset.log; sudo env >> env_reset.log

Можете просмотреть и сравнить эти логи, чтобы понять отличия между env_reset и !env_reset.

3. Сохранить переменные среды, добавив их в env_keep в /etc/sudoers.

  Defaults env_keep += "PWD"

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

         $ sudo -s
         # sudo -V

Если всё пройдёт гладко, вы получите скомпилированный модуль hello-1.ko. Информацию о нём можно вывести командой:

modinfo hello-1.ko

На этом этапе команда:

sudo lsmod | grep hello

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

sudo insmod hello-1.ko

При этом символ тире превратится в нижнее подчёркивание. Теперь, когда вы снова выполните:

sudo lsmod | grep hello

то увидите загруженный модуль. Удалить его можно с помощью:

sudo rmmod hello_1

Обратите внимание — тире было заменено нижним подчёркиванием. Чтобы увидеть произошедшее в логах, выполните:

sudo journalctl --since "1 hour ago" | grep kernel

Теперь вам известны основы создания, компиляции, установки и удаления модулей. Далее мы подробнее разберём, как они работают.

Модули ядра должны иметь не менее двух функций:

  • стартовую (инициализация), которая называется init_module() и вызывается при внедрении (insmod) модуля в ядро;
  • завершающую (очистка), которая зовётся cleanup_module() и вызывается непосредственно перед извлечением модуля из ядра.

В действительности же с версии 2.3.13 произошли кое-какие изменения. Теперь стартовую и завершающую функцию модулей можно называть на своё усмотрение, и об этом будет подробнее сказано в разделе 4.2. На деле этот новый метод даже предпочтительней, хотя многие по прежнему используют названия init_module() и cleanup_module().

Как правило, init_module() или регистрирует обработчик чего-либо с помощью ядра, или заменяет одну из функций ядра собственным кодом (обычно кодом, который выполняет определённые действия и вызывает исходную функцию). Что касается cleanup_module(), то она должна отменять всё, что сделала init_module(), чтобы безопасно выгрузить модуль.

Наконец, каждый модуль ядра должен включать <linux/module.h>. Нам нужно было включить <linux/kernel.h> только для расширения макроса уровня журнала pr_alert(), о чём подробнее сказано в пункте 2.

  1. Примечание о стиле написания кода. Есть нюанс, который может не быть очевиден тем, кто только начинает заниматься программированием ядра. Имеется в виду то, что отступы в коде должны делаться с помощью табов, а не пробелов. Это одно из общих соглашений. Оно вам может не нравиться, но придётся привыкать, если вы соберётесь отправлять патч в основную ветку ядра.
  2. Добавление макросов вывода. Изначально использовалась функция printk, обычно сопровождаемая приоритетом уровня журнала KERN_DEBUG или KERN_INFO. Недавно же появилась возможность выражать это в сокращённой форме с помощью макросов вывода pr_info и pr_debug. Такой подход просто избавляет от лишних нажатий клавиш и выглядит более лаконично. Найти эти макросы можно в include/linux/printk.h. Рекомендую уделить время и прочесть о доступных макросах приоритетов.
  3. Насчёт компиляции. Модули ядра нужно компилировать несколько иначе, нежели обычные приложения пространства пользователя. Прежние версии ядра требовали от нас особого внимания к этим настройкам, которые обычно хранились в Makefile. И несмотря на иерархическую организованность, в make-файлах нижнего уровня скапливалось множество лишних настроек, что делало эти файлы большими и усложняло их обслуживание. К счастью, появился новый способ делать всё это, который называется kbuild, и процесс сборки для внешних загружаемых модулей теперь полностью интегрирован в стандартный механизм сборки ядра. Подробнее о компиляции модулей, не являющихся частью официального ядра (таких как примеры в этом руководства), читайте в Documentation/kbuild/modules.rst.

Дополнительные подробности о make-файлах для модулей ядра доступны в Documentation/kbuild/makefiles.rst. Обязательно прочтите эту документацию и изучите связанные с ней файлы – это наверняка избавит вас от большого объёма лишней работы.

А вот вам одно бонусное упражнение. Видите комментарий над инструкцией return в init_module()? Измените возвращаемое значение на отрицательное, после чего перекомпилируйте и заново загрузите модуль. Что произойдёт?

▍ 4.2 Hello и Goodbye

В ранних версиях ядра вам нужно было использовать функции init_module и cleanup_module, как в нашем первом примере «Hello world», но сегодня их уже можно именовать на своё усмотрение с помощью макросов module_init и module_exit, которые определены в include/linux/module.h. Единственное требование – это чтобы функции инициализации и очистки были определены до вызова этих макросов, в противном случае возникнут ошибки компиляции.

Вот пример:

/*
 * hello-2.c – демонстрация макросов module_init() и module_exit().
 * Этот вариант предпочтительнее использования init_module() и cleanup_module().
 */
#include <linux/init.h> /* Необходим для макросов */
#include <linux/kernel.h> /* Необходим для pr_info() */
#include <linux/module.h> /* Необходим всем модулям */
 
static int __init hello_2_init(void)
{
    pr_info("Hello, world 2n");
    return 0;
}
 
static void __exit hello_2_exit(void)
{
    pr_info("Goodbye, world 2n");
}
 
module_init(hello_2_init);
module_exit(hello_2_exit);
 
MODULE_LICENSE("GPL");

Теперь у нас есть уже два реальных модуля ядра. Добавить ещё один будет совсем несложно:

obj-m += hello-1.o
obj-m += hello-2.o
 
PWD := $(CURDIR)
 
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
 
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Загляните в drivers/char/Makefile, чтобы увидеть реальный пример. Как видите, некоторые элементы включаются в ядро жёстко (obj-y), но куда делись все obj-m? Те, кто знаком со скриптами оболочки, смогут без проблем их обнаружить. Для остальных подскажу, что записи obj-$(CONFIG_FOO), которые вы видите повсюду, расширяются на obj-y или obj-m в зависимости от того, на какое значение была установлена переменная CONFIG_FOOy или m. Попутно отмечу, что именно эти переменные вы установили в файле .config в каталоге верхнего уровня дерева исходного кода в последний раз, когда выполнили make menuconfig или что-то в том духе.

▍ 4.3 Макросы __init и __exit

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

Также есть __initdata, которая работает аналогично __init, но для переменных инициализации, а не для функций.

Макрос __exit приводит к пропуску функции, если модуль встроен в ядро, то есть аналогично __init не влияет на загружаемые модули. Опять же, если учесть, когда выполняется функция очистки, то это полностью оправданно. Встроенным драйверам не требуется очистка, а вот загружаемым модулям как раз да.

Эти макросы определены в include/linux/init.h и используются для освобождения памяти ядра. Если при его загрузке вы видите сообщение вроде Freeing unused kernel memory: 236k freed, то знайте – это тот самый процесс.

/*
 * hello-3.c – демонстрация макросов __init, __initdata и __exit.
 */
#include <linux/init.h> /* Необходим для макросов */
#include <linux/kernel.h> /* Необходим для pr_info() */
#include <linux/module.h> /* Необходим для всех модулей */
 
static int hello3_data __initdata = 3;
 
static int __init hello_3_init(void)
{
    pr_info("Hello, world %dn", hello3_data);
    return 0;
}
 
static void __exit hello_3_exit(void)
{
    pr_info("Goodbye, world 3n");
}
 
module_init(hello_3_init);
module_exit(hello_3_exit);
 
MODULE_LICENSE("GPL");

▍ 4.4 Лицензирование и документирование модулей

Даже не знаю, кто вообще загружает или вообще задумывается об использовании проприетарных модулей? Если вы из числа таких людей, то наверняка видели нечто подобное:

$ sudo insmod xxxxxx.ko
loading out-of-tree module taints kernel.
module license 'unspecified' taints kernel.

Для обозначения лицензии вашего модуля вы можете использовать ряд макросов, например: «GPL», «GPL v2», «GPL and additional rights», «Dual BSD/GPL», «Dual MIT/GPL», «Dual MPL/GPL» и «Proprietary». Определены они в include/linux/module.h.

Для указания используемой лицензии существует макрос MODULE_LICENSE. Он и ещё пара макросов, описывающих модуль, приведены в примере ниже.

/*
 * hello-4.c – Демонстрирует документирование модуля.
 */
#include <linux/init.h> /* Необходим для макросов */
#include <linux/kernel.h> /* Необходим для pr_info() */
#include <linux/module.h> /* Необходим для всех модулей */
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LKMPG");
MODULE_DESCRIPTION("A sample driver");
 
static int __init init_hello_4(void)
{
    pr_info("Hello, world 4n");
    return 0;
}
 
static void __exit cleanup_hello_4(void)
{
    pr_info("Goodbye, world 4n");
}
 
module_init(init_hello_4);
module_exit(cleanup_hello_4);

▍ 4.5 Передача в модуль аргументов командной строки

Модулям можно передавать аргументы командной строки, но не через argc/argv, к которым вы, возможно, привыкли.

Чтобы получить такую возможность, нужно объявить переменные, которые будут принимать значения аргументов командной строки как глобальные и затем использовать макрос module_param() (определяемый в include/linux/moduleparam.h) для настройки этого механизма. Во время выполнения insmod будет заполнять эти переменные получаемыми аргументами, например, insmod mymodule.ko myvariable=5. Для большей ясности объявления переменных и макросов необходимо размещать в начале модулей. Более наглядно всё это продемонстрировано в примере кода.

Макрос module_param() получает 3 аргумента: имя переменной, её тип и разрешения для соответствующего файла в sysfs. Целочисленные типы могут быть знаковыми, как обычно, или беззнаковыми. Если вы хотите использовать массивы целых чисел или строк, к вашим услугам module_param_array() и module_param_string().

int myint = 3;
module_param(myint, int, 0);

Массивы тоже поддерживаются, но в современных версиях работает это несколько иначе, нежели раньше. Для отслеживания количества параметров необходимо передать указатель на число переменных в качестве третьего аргумента. При желании вы можете вообще проигнорировать подсчёт и передать NULL. Вот пример обоих вариантов:

int myintarray[2];
module_param_array(myintarray, int, NULL, 0); /* если подсчёт не интересует */
 
short myshortarray[4];
int count;
module_param_array(myshortarray, short, &count, 0); /* подсчёт происходит в переменной "count" */

Хорошим применением для этого варианта будет предустановка значений переменных модуля, таких как порт или адрес ввода-вывода. Если переменные содержат предустановленные значения, выполнять автообнаружение. В противном случае оставлять текущее значение. Позже об этом будет сказано подробнее.

Наконец, есть ещё макрос MODULE_PARM_DESC(), используемый для документирования аргументов, которые может принять модуль. Он получает два параметра: имя переменной и строку в свободной форме, эту переменную описывающую.

Пример передачи аргументов командной строки в модуль

/*
 * hello-5.c – демонстрирует передачу аргументов командной строки в модуль.
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
 
MODULE_LICENSE("GPL");
 
static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";
static int myintarray[2] = { 420, 420 };
static int arr_argc = 0;
 
/* module_param(foo, int, 0000)
 * Первым аргументом указывается имя параметра.
 * Вторым указывается его тип.
 * Третьим указываются биты разрешений
 * для представления параметров в sysfs (если не нуль) позднее.
 */
module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, "A short integer");
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "An integer");
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, "A long integer");
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, "A character string");
 
/* module_param_array(name, type, num, perm);
 * Первым аргументом идёт имя параметра (в данном случае массива).
 * Второй аргумент – это тип элементов массива.
 * Третий – это указатель на переменную, которая будет хранить количество элементов массива, инициализированных пользователем при загрузке модуля.
 * Четвёртый аргумент – это биты разрешения.
 */
module_param_array(myintarray, int, &arr_argc, 0000);
MODULE_PARM_DESC(myintarray, "An array of integers");
 
static int __init hello_5_init(void)
{
    int i;
 
    pr_info("Hello, world 5n=============n");
    pr_info("myshort is a short integer: %hdn", myshort);
    pr_info("myint is an integer: %dn", myint);
    pr_info("mylong is a long integer: %ldn", mylong);
    pr_info("mystring is a string: %sn", mystring);
 
    for (i = 0; i < ARRAY_SIZE(myintarray); i++)
        pr_info("myintarray[%d] = %dn", i, myintarray[i]);
 
    pr_info("got %d arguments for myintarray.n", arr_argc);
    return 0;
}
 
static void __exit hello_5_exit(void)
{
    pr_info("Goodbye, world 5n");
}
 
module_init(hello_5_init);
module_exit(hello_5_exit);

Рекомендую поэкспериментировать со следующим кодом:

$ sudo insmod hello-5.ko mystring="bebop" myintarray=-1
$ sudo dmesg -t | tail -7
myshort is a short integer: 1
myint is an integer: 420
mylong is a long integer: 9999
mystring is a string: bebop
myintarray[0] = -1
myintarray[1] = 420
got 1 arguments for myintarray.

$ sudo rmmod hello-5
$ sudo dmesg -t | tail -1
Goodbye, world 5

$ sudo insmod hello-5.ko mystring="supercalifragilisticexpialidocious" myintarray=-1,-1
$ sudo dmesg -t | tail -7
myshort is a short integer: 1
myint is an integer: 420
mylong is a long integer: 9999
mystring is a string: supercalifragilisticexpialidocious
myintarray[0] = -1
myintarray[1] = -1
got 2 arguments for myintarray.

$ sudo rmmod hello-5
$ sudo dmesg -t | tail -1
Goodbye, world 5

$ sudo insmod hello-5.ko mylong=hello
insmod: ERROR: could not insert module hello-5.ko: Invalid parameters

▍ 4.6 Модули, состоящие из нескольких файлов

Иногда есть смысл поделить модуль на несколько файлов.

Вот пример такого модуля:

/*
 * start.c – пример модулей, состоящих из нескольких файлов.
 */
 
#include <linux/kernel.h> /* Выполнение работы ядра. */
#include <linux/module.h> /* В частности, модуля. */
 
int init_module(void)
{
    pr_info("Hello, world - this is the kernel speakingn");
    return 0;
}
 
MODULE_LICENSE("GPL");

Второй файл:

/*
 * stop.c – пример модулей, состоящих из нескольких файлов.
 */
 
#include <linux/kernel.h> /* Выполнение работы ядра. */
#include <linux/module.h> /* В частности, модуля. */
 
void cleanup_module(void)
{
    pr_info("Short is the life of a kernel modulen");
}
 
MODULE_LICENSE("GPL");

И, наконец, Makefile:

obj-m += hello-1.o
obj-m += hello-2.o
obj-m += hello-3.o
obj-m += hello-4.o
obj-m += hello-5.o
obj-m += startstop.o
startstop-objs := start.o stop.o
 
PWD := $(CURDIR)
 
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
 
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Это полный Makefile для всех примеров, которые мы успели рассмотреть. Первые пять строчек не представляют ничего особенного, но для последнего примера нам потребуется две строки. В первой мы придумываем имя объекта для нашего комбинированного модуля, а во второй сообщаем make, какие объектные файлы являются его частью.

▍ 4.7 Сборка модулей для скомпилированного ядра

Естественно, мы настоятельно рекомендуем вам перекомпилировать ядро, чтобы иметь возможность активировать ряд полезных функций отладки, таких как принудительная выгрузка модулей ( MODULE_FORCE_UNLOAD ): когда эта опция включена, можно с помощью команды sudo rmmod -f module принудить ядро выгрузить модуль, даже если оно сочтёт это небезопасным. В процессе разработки модуля эта опция может сэкономить вам много времени и избавить от лишних перезагрузок. Если вы не хотите перекомпилировать ядро, то рассмотрите вариант выполнения примеров внутри тестового дистрибутива на виртуальной машине. В таком случае при нарушении работоспособности вы сможете легко перезагрузиться или восстановить VM.

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

Итак, если вы просто установите дерево исходного кода, используете его для компиляции модуля и попытаетесь внедрить этот модуль в ядро, то в большинстве случаев получите ошибку:

insmod: ERROR: could not insert module poet.ko: Invalid module format

Более понятная информация логируется в системный журнал:

kernel: poet: disagrees about version of symbol module_layout

Иными словами, ваше ядро отказывается принимать модуль, потому что строки версии (точнее, vermagic, см. include/linux/vermagic.h) не совпадают. К слову, строки версии хранятся в объекте модуля в виде статической строки, начинающейся с vermagic:. Данные версии вставляются в модуль, когда он линкуется с файлом kernel/module.o. Для просмотра сигнатуры версии и прочих строк, хранящихся в конкретном модуле, выполните команду modinfo module.ko:

$ modinfo hello-4.ko
description:    A sample driver
author:         LKMPG
license:        GPL
srcversion:     B2AA7FBFCC2C39AED665382
depends:
retpoline:      Y
name:           hello_4
vermagic:       5.4.0-70-generic SMP mod_unload modversions

Для преодоления этой проблемы можно задействовать опцию --force-vermagic, но такое решение не гарантирует безопасность и однозначно будет неприемлемым в создании модулей. Следовательно, модуль нужно скомпилировать в среде, которая была идентична той, где создано наше скомпилированное ядро. Этому и будет посвящён остаток текущей главы.

Во-первых, убедитесь, что дерево исходного кода ядра вам доступно и имеет одинаковую версию с вашим текущим ядром. Далее найдите файл конфигурации, который использовался для компиляции ядра. Обычно он доступен в текущем каталоге boot под именем вроде config-5.14.x. Его будет достаточно скопировать в дерево исходного кода вашего ядра:

cp /boot/config-`uname -r` .config

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

Это небольшое отличие, а именно пользовательская строка, которая присутствует в версии модуля, но отсутствует в версии ядра, вызвано изменением относительно оригинала в Makefile, который содержат некоторые дистрибутивы. Далее вам нужно просмотреть собственный Makefile и обеспечить, чтобы представленная информация версии в точности соответствовала той, что указана в текущем ядре. Например, ваш Makefile может начинаться так:

VERSION = 5
PATCHLEVEL = 14
SUBLEVEL = 0
EXTRAVERSION = -rc2

В этом случае необходимо восстановить значение символа EXTRAVERSION на -rc2. Мы рекомендуем держать резервную копию Makefile, используемого для компиляции ядра, в /lib/modules/5.14.0-rc2/build. Для этого будет достаточно выполнить:

cp /lib/modules/`uname -r`/build/Makefile linux-`uname -r`

Здесь linux-`uname -r` — это исходный код ядра, которое вы собираетесь собрать.

Теперь выполните make для обновления конфигурации вместе с заголовками версии и объектами:

$ make
  SYNC    include/config/auto.conf.cmd
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  LEX     scripts/kconfig/lexer.lex.c
  YACC    scripts/kconfig/parser.tab.[ch]
  HOSTCC  scripts/kconfig/preprocess.o
  HOSTCC  scripts/kconfig/symbol.o
  HOSTCC  scripts/kconfig/util.o
  HOSTCC  scripts/kconfig/lexer.lex.o
  HOSTCC  scripts/kconfig/parser.tab.o
  HOSTLD  scripts/kconfig/conf

Если же вы не хотите фактически компилировать ядро, то можете прервать процесс сборки (CTRL-C) сразу же после строки SPLIT, поскольку в этот момент необходимые вам файлы уже готовы.

Теперь можно вернуться в каталог модуля и скомпилировать его: он будет собран в точном соответствии с настройками текущего ядра и загрузится в него без каких-либо ошибок.

Продолжение

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

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

Проводить эксперименты было решено на Raspberry Pi 3 Model B+. На то есть три основные причины. Во-первых, малинка широко доступна и стоит недорого (особенно третья малинка, после выхода четвертой), что делает эксперименты повторяемыми. Во-вторых, запускать модули ядра на той же машине, где вы их разрабатываете, в любом случае не лучшая затея. Ведь ошибка в ядерном коде может приводить к какими угодно последствиям, не исключая повреждения ФС. И в-третьих, в отличие от виртуальной машины, малинка не отъедает ресурсы на вашей основной системе и позволяет реально взаимодействовать с реальным железом.

Образ системы был записан на SD-карту при помощи Raspberry Pi Imager. Приложение использовало образ на основе Raspbian 10 с ядром Linux 5.10. Это LTS-версия ядра, поддержка которого прекратится в декабре 2026-го года.

Для написания модулей ядра необходимо установить пакет с заголовочными файлами. В Raspbian это делается так:

sudo apt install raspberrypi-kernel-headers
ls /lib/modules/$(uname -r)

В других системах пакет может называться linux-headers-* или как-то иначе.

Создадим новую директорию с файлом hello.c:

#include <linux/kernel.h>
#include <linux/module.h>

int init_module(void) {
    pr_info(«Hello worldn«);
    return 0;
}

void cleanup_module(void) {
    pr_info(«Goodbye worldn«);
}

MODULE_LICENSE(«GPL»);

Рядом положим файл Makefile:

objm += hello.o

all:
    $(MAKE) C /lib/modules/$(shell uname r)/build M=$(PWD) modules

clean:
    $(MAKE) C /lib/modules/$(shell uname r)/build M=$(PWD) clean

Говорим make. В результате должен появиться файл hello.ko.

Теперь попробуем следующие команды:

# посмотреть информацию о модуле
modinfo hello.ko

# загрузить модуль
sudo insmod hello.ko

# список загруженных модулей ядра
lsmod | grep hello

# выгрузить модуль
sudo rmmod hello

# почитать логи
tail /var/log/syslog

При загрузке модуля будет вызвана процедура init_module(), а при выгрузке — cleanup_module(). Они напишут соответствующие логи через pr_info(), и мы увидим их в /var/log/syslog. С этим все понятно. Давайте перейдем к чему-то поинтересней.

Начнем с более детального рассмотрения pr_info(). Среди символов, экспортируемых ядром, вы его не найдете. Поиск по заголовочным файлам показывает, что на самом деле это макрос, объявленный в linux/printk.h:

#define pr_info(fmt, …)
        printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

А вот printk() уже является экспортируемым символом:

$ sudo cat /proc/kallsyms | grep ‘T printk’

801833e0 T printk_nmi_direct_exit
809f1508 T printk
809f16f4 T printk_deferred

Первая колонка — это адрес символа. Он отображаются, только если читать /proc/kallsyms под суперпользователем. В противном случае, мы увидим нули. Во второй колонке показано, откуда экспортируется символ. Согласно man nm, T и t соответствуют секции кода (.text). Заглавная буква означает, что символ виден глобально, а значит, может использоваться в модулях ядра. Теперь мы чуть лучше понимаем, как происходит общение между ядром и его модулями.

Далее, рассмотрим модуль посложнее:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

MODULE_LICENSE(«GPL»);
MODULE_AUTHOR(«Aleksander Alekseev»);
MODULE_DESCRIPTION(«A simple driver»);

static char* name = «%username%»;

module_param(name, charp, 0);
MODULE_PARM_DESC(name, «Enter your name»);

static int __init init(void) {
    pr_info(«Hello, %sn«, name);
    return 0;
}

static void __exit cleanup(void) {
    pr_info(«Goodbye, %sn«, name);
}

module_init(init);
module_exit(cleanup);

Из этого примера мы узнаем ряд важных вещей. Во-первых, что процедуры, вызываемые при загрузке и выгрузке модуля, могут называться как угодно. Во-вторых, что в модуле можно указать не только его лицензию, но также автора и краткое описание. Сравните вывод modinfo для этого модуля и предыдущего. И в-третьих, модуль может принимать параметры:

sudo insmod param.ko name=Alex
sudo rmmod param
tail /var/log/syslog

В логах мы предсказуемо увидим:

Hello, Alex
Goodbye, Alex

Параметры, переданные модулю, видны через sysfs. Но чтобы это работало, код нужно немного изменить:

// module_param(name, charp, 0);
module_param(name, charp, S_IRUGO);

Если теперь пересобрать модуль, то можно сделать так:

cat /sys/module/param/parameters/name

Думаю, что для первого раза удивительных открытий достаточно. Полная версия кода доступна в этом репозитории на GitHub. Там же есть список дополнительных материалов для самостоятельного изучения.

Дополнение: В продолжение темы см статьи Модули ядра Linux: пример символьного устройства, Модули ядра Linux: таймеры и GPIO и Модули ядра Linux: обработка прерываний.

Метки: Linux, Ring0.

Kernel modules are pieces of code that can be loaded and unloaded into the kernel upon demand. They extend the functionality of the kernel without the need to reboot the system.
Custom codes can be added to Linux kernels via two methods.

  • The basic way is to add the code to the kernel source tree and recompile the kernel.
  • A more efficient way is to do this is by adding code to the kernel while it is running. This process is called loading the module, where module refers to the code that we want to add to the kernel.

Since we are loading these codes at runtime and they are not part of the official Linux kernel, these are called loadable kernel module(LKM), which is different from the “base kernel”. Base kernel is located in /boot directory and is always loaded when we boot our machine whereas LKMs are loaded after the base kernel is already loaded. Nonetheless, these LKM are very much part of our kernel and they communicate with base kernel to complete their functions.

LKMs can perform a variety of task, but basically they come under three main categories,

  • device driver,
  • filesystem driver and
  • System calls.

So what advantage do LKMs offer?
One major advantage they have is that we don’t need to keep rebuilding the kernel every time we add a new device or if we upgrade an old device. This saves time and also helps in keeping our base kernel error free. A useful rule of thumb is that we should not change our base kernel once we have a working base kernel.
Also, it helps in diagnosing system problems. For example, assume we have added a module to the base kernel(i.e., we have modified our base kernel by recompiling it) and the module has a bug in it. This will cause error in system boot and we will never know which part of the kernel is causing problems. Whereas if we load the module at runtime and it causes problems, we will immediately know the issue and we can unload the module until we fix it.
LKMs are very flexible, in the sense that they can be loaded and unloaded with a single line of command. This helps in saving memory as we load the LKM only when we need them. Moreover, they are not slower than base kernel because calling either one of them is simply loading code from a different part of memory.

**Warning: LKMs are not user space programs. They are part of the kernel. They have free run of the system and can easily crash it.

So now that we have established the use loadable kernel modules, we are going to write a hello world kernel module. That will print a message when we load the module and an exit message when we unload the module.

Code:

#include <linux/module.h>     /* Needed by all modules */

#include <linux/kernel.h>     /* Needed for KERN_INFO */

#include <linux/init.h>       /* Needed for the macros */

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Akshat Sinha");

MODULE_DESCRIPTION("A simple Hello world LKM!");

MODULE_VERSION("0.1");

static int __init hello_start(void)

{

    printk(KERN_INFO "Loading hello module...n");

    printk(KERN_INFO "Hello worldn");

    return 0;

}

static void __exit hello_end(void)

{

    printk(KERN_INFO "Goodbye Mr.n");

}

module_init(hello_start);

module_exit(hello_end);

Explanation for the above code:
Kernel modules must have at least two functions: a “start” (initialization) function called init_module() which is called when the module is insmoded into the kernel, and an “end” (cleanup) function called cleanup_module() which is called just before it is rmmoded. Actually, things have changed starting with kernel 2.3.13. You can now use whatever name you like for the start and end functions of a module. In fact, the new method is the preferred method. However, many people still use init_module() and cleanup_module() for their start and end functions. In this code we have used hello_start() as init function and hello_end() as cleanup function.
Another thing that you might have noticed is that instead of printf() function we have used printk(). This is because module will not print anything on the console but it will log the message in /var/log/kern.log. Hence it is used to debug kernel modules. Moreover, there are eight possible loglevel strings, defined in the header , that are required while using printk(). We have list them in order of decreasing severity:

  • KERN_EMERG: Used for emergency messages, usually those that precede a crash.
  • KERN_ALERT: A situation requiring immediate action.
  • KERN_CRIT: Critical conditions, often related to serious hardware or software failures.
  • KERN_ERR: Used to report error conditions; device drivers often use KERN_ERR to report hardware difficulties.
  • KERN_WARNING: Warnings about problematic situations that do not, in themselves, create serious problems with the system.
  • KERN_NOTICE: Situations that are normal, but still worthy of note. A number of security-related conditions are reported at this level.
  • KERN_INFO: Informational messages. Many drivers print information about the hardware they find at startup time at this level.
  • KERN_DEBUG: Used for debugging messages.
  • We have used KERN_INFO to print the message.

    Preparing the system to run the code:
    The system must be prepared to build kernel code, and to do this you must have the Linux headers installed on your device. On a typical Linux desktop machine you can use your package manager to locate the correct package to install. For example, under 64-bit Debian you can use:

    akshat@gfg:~$ sudo apt-get install build-essential linux-headers-$(uname -r)
    

    Makefile to compile the source code:

    obj-m = hello.o
    all:
            make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
    clean:
            make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    

    **Note: Don’t forget the tab spaces in Makefile

    Compiling and loading the module:
    Run the make command to compile the source code. Then use insmod to load the module.

    akshat@gfg:~$ make
    make -C /lib/modules/4.2.0-42-generic/build/ M=/home/akshat/Documents/hello-module modules
    make[1]: Entering directory `/usr/src/linux-headers-4.2.0-42-generic'
      CC [M]  /home/akshat/Documents/hello-module/hello.o
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /home/akshat/Documents/hello-module/hello.mod.o
      LD [M]  /home/akshat/Documents/hello-module/hello.ko
    make[1]: Leaving directory `/usr/src/linux-headers-4.2.0-42-generic'
    

    Now we will use insmod to load the hello.ko object.

    akshat@gfg:~$ sudo insmod hello.ko
    

    Testing the module:
    You can get information about the module using the modinfo command, which will identify the description, author and any module parameters that are defined:

    akshat@gfg:~$ modinfo hello.ko
    filename:       /home/akshat/Documents/hello-module/hello.ko
    version:        0.1
    description:    A simple Hello world LKM
    author:         Akshat Sinha
    license:        GPL
    srcversion:     2F2B1B95DA1F08AC18B09BC
    depends:        
    vermagic:       4.2.0-42-generic SMP mod_unload modversions
    

    To see the message, we need to read the kern.log in /var/log directory.

    akshat@gfg:~$ tail /var/log/kern.log
    ...
    ...
    Sep 10 17:43:39 akshat-gfg kernel: [26380.327886] Hello world
    To unload the module, we run rmmod:
    akshat@gfg:~$ sudo rmmod hello
    Now run the tail command to get the exit message.
    akshat@gfg:~$ tail /var/log/kern.log
    ...
    Sep 10 17:43:39 akshat-gfg kernel: [26380.327886] Hello world
    Sep 10 17:45:42 akshat-gfg kernel: [26503.773982] Goodbye Mr.
    

    This article is contributed by Akshat Sinha. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

    Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.

Kernel modules are pieces of code that can be loaded and unloaded into the kernel upon demand. They extend the functionality of the kernel without the need to reboot the system.
Custom codes can be added to Linux kernels via two methods.

  • The basic way is to add the code to the kernel source tree and recompile the kernel.
  • A more efficient way is to do this is by adding code to the kernel while it is running. This process is called loading the module, where module refers to the code that we want to add to the kernel.

Since we are loading these codes at runtime and they are not part of the official Linux kernel, these are called loadable kernel module(LKM), which is different from the “base kernel”. Base kernel is located in /boot directory and is always loaded when we boot our machine whereas LKMs are loaded after the base kernel is already loaded. Nonetheless, these LKM are very much part of our kernel and they communicate with base kernel to complete their functions.

LKMs can perform a variety of task, but basically they come under three main categories,

  • device driver,
  • filesystem driver and
  • System calls.

So what advantage do LKMs offer?
One major advantage they have is that we don’t need to keep rebuilding the kernel every time we add a new device or if we upgrade an old device. This saves time and also helps in keeping our base kernel error free. A useful rule of thumb is that we should not change our base kernel once we have a working base kernel.
Also, it helps in diagnosing system problems. For example, assume we have added a module to the base kernel(i.e., we have modified our base kernel by recompiling it) and the module has a bug in it. This will cause error in system boot and we will never know which part of the kernel is causing problems. Whereas if we load the module at runtime and it causes problems, we will immediately know the issue and we can unload the module until we fix it.
LKMs are very flexible, in the sense that they can be loaded and unloaded with a single line of command. This helps in saving memory as we load the LKM only when we need them. Moreover, they are not slower than base kernel because calling either one of them is simply loading code from a different part of memory.

**Warning: LKMs are not user space programs. They are part of the kernel. They have free run of the system and can easily crash it.

So now that we have established the use loadable kernel modules, we are going to write a hello world kernel module. That will print a message when we load the module and an exit message when we unload the module.

Code:

#include <linux/module.h>     /* Needed by all modules */

#include <linux/kernel.h>     /* Needed for KERN_INFO */

#include <linux/init.h>       /* Needed for the macros */

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Akshat Sinha");

MODULE_DESCRIPTION("A simple Hello world LKM!");

MODULE_VERSION("0.1");

static int __init hello_start(void)

{

    printk(KERN_INFO "Loading hello module...n");

    printk(KERN_INFO "Hello worldn");

    return 0;

}

static void __exit hello_end(void)

{

    printk(KERN_INFO "Goodbye Mr.n");

}

module_init(hello_start);

module_exit(hello_end);

Explanation for the above code:
Kernel modules must have at least two functions: a “start” (initialization) function called init_module() which is called when the module is insmoded into the kernel, and an “end” (cleanup) function called cleanup_module() which is called just before it is rmmoded. Actually, things have changed starting with kernel 2.3.13. You can now use whatever name you like for the start and end functions of a module. In fact, the new method is the preferred method. However, many people still use init_module() and cleanup_module() for their start and end functions. In this code we have used hello_start() as init function and hello_end() as cleanup function.
Another thing that you might have noticed is that instead of printf() function we have used printk(). This is because module will not print anything on the console but it will log the message in /var/log/kern.log. Hence it is used to debug kernel modules. Moreover, there are eight possible loglevel strings, defined in the header , that are required while using printk(). We have list them in order of decreasing severity:

  • KERN_EMERG: Used for emergency messages, usually those that precede a crash.
  • KERN_ALERT: A situation requiring immediate action.
  • KERN_CRIT: Critical conditions, often related to serious hardware or software failures.
  • KERN_ERR: Used to report error conditions; device drivers often use KERN_ERR to report hardware difficulties.
  • KERN_WARNING: Warnings about problematic situations that do not, in themselves, create serious problems with the system.
  • KERN_NOTICE: Situations that are normal, but still worthy of note. A number of security-related conditions are reported at this level.
  • KERN_INFO: Informational messages. Many drivers print information about the hardware they find at startup time at this level.
  • KERN_DEBUG: Used for debugging messages.
  • We have used KERN_INFO to print the message.

    Preparing the system to run the code:
    The system must be prepared to build kernel code, and to do this you must have the Linux headers installed on your device. On a typical Linux desktop machine you can use your package manager to locate the correct package to install. For example, under 64-bit Debian you can use:

    akshat@gfg:~$ sudo apt-get install build-essential linux-headers-$(uname -r)
    

    Makefile to compile the source code:

    obj-m = hello.o
    all:
            make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
    clean:
            make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    

    **Note: Don’t forget the tab spaces in Makefile

    Compiling and loading the module:
    Run the make command to compile the source code. Then use insmod to load the module.

    akshat@gfg:~$ make
    make -C /lib/modules/4.2.0-42-generic/build/ M=/home/akshat/Documents/hello-module modules
    make[1]: Entering directory `/usr/src/linux-headers-4.2.0-42-generic'
      CC [M]  /home/akshat/Documents/hello-module/hello.o
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /home/akshat/Documents/hello-module/hello.mod.o
      LD [M]  /home/akshat/Documents/hello-module/hello.ko
    make[1]: Leaving directory `/usr/src/linux-headers-4.2.0-42-generic'
    

    Now we will use insmod to load the hello.ko object.

    akshat@gfg:~$ sudo insmod hello.ko
    

    Testing the module:
    You can get information about the module using the modinfo command, which will identify the description, author and any module parameters that are defined:

    akshat@gfg:~$ modinfo hello.ko
    filename:       /home/akshat/Documents/hello-module/hello.ko
    version:        0.1
    description:    A simple Hello world LKM
    author:         Akshat Sinha
    license:        GPL
    srcversion:     2F2B1B95DA1F08AC18B09BC
    depends:        
    vermagic:       4.2.0-42-generic SMP mod_unload modversions
    

    To see the message, we need to read the kern.log in /var/log directory.

    akshat@gfg:~$ tail /var/log/kern.log
    ...
    ...
    Sep 10 17:43:39 akshat-gfg kernel: [26380.327886] Hello world
    To unload the module, we run rmmod:
    akshat@gfg:~$ sudo rmmod hello
    Now run the tail command to get the exit message.
    akshat@gfg:~$ tail /var/log/kern.log
    ...
    Sep 10 17:43:39 akshat-gfg kernel: [26380.327886] Hello world
    Sep 10 17:45:42 akshat-gfg kernel: [26503.773982] Goodbye Mr.
    

    This article is contributed by Akshat Sinha. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

    Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.

ЛР1: Введение в разработку модулей ядра

Название директории для лабораторной работы: lab1_kernel_development_introduction

Необходимые пакеты (названия приведены для Arch-подобных дистрибутивов):

  • Группа пакетов base-devel (включает в себя пакеты
    autoconf
    automake
    binutils
    bison
    fakeroot
    file
    findutils
    flex
    gawk
    gcc
    gettext
    grep
    groff
    gzip
    libtool
    m4
    make
    pacman
    patch
    pkgconf
    sed
    sudo
    texinfo
    which
    ). По сути это основные инструменты сборки. На многие из них полагается система сборки ядра.

  • Некоторые дополнительные служебные утилиты: cpio (потребуется в процессе создания initramfs-образа для ядра)
    и bc (калькулятор, требуется системе сборки ядра).

  • QEMU (пакеты qemu и qemu-arch-extra). Это мультиплатформенный эмулятор и гипервизор.

    Зачем нам нужен QEMU? Если пользовательский процесс не отвечает или, скажем, выполнил недопустимую
    операцию (например, обратился к неаллоцированному адресу памяти), ядро может принудительно закрыть его.
    Другие процессы продолжат свою работу, как ни в чем не бывало.

    Но мы разрабатываем модули ядра и при их подключении, они линкуются к ядру и
    становятся с ним, в неком роде, одним целым. Что будет, если такого рода ошибка
    возникает в коде ядра? Хоть ядро и спроектировано как можно более надежным и
    попытается восстановиться при возникновении ошибки модуля ядра, при разработке
    модулей ядра, ошибки часто приводят к его падению и к невозможности дальше
    продолжать нормальную работу до перезагрузки.

    Конечно, мы могли бы разрабатывать модули ядра непосредственно на хост-системе,
    но это неудобно и небезопасно (что, если ваш модуль случайно обратиться к зоне памяти
    файловой системы и вызовет повреждение какого-нибудь важного файла).
    Более того, отлаживать ядро с использованием традиционных инструментов, таких как
    GDB не так просто, как пользовательские приложения.

    Если запускать GDB отладку на одной машине, это по сути будет «отладкой ядра с
    использованием отлаживаемого кода ядра». Поэтому, на практике часто применяют две
    машины, соединенные между собой. На одной машине выполняется отлаживаемое ядро,
    а другая выполняет роль управляющей.
    Другой альтернативой является QEMU. У него есть встроенный интерфейс GDB, что
    позволяет выполнять отладку на самом низком уровне.

    Таким образом, QEMU очень удобен при разработке ядра и практически незаменим.

На Arch-подобных системах установить все сразу можно, выполнив

sudo pacman -S base-devel qemu qemu-arch-extra bc cpio

Краткие теоретические сведения

Прежде всего, Linux — это не операционная система, а ядро. Дистрибутивы операционных систем,
использующие ядро Linux принято называть объединенно GNU/Linux. Они, в свою очередь, включают
в себя Linux-ядро, Unix-подобное user space окружение и, зачастую, менеджер пакетов.
Unix-подобное окружение предоставляет набор «стандартных» команд таких, как
ls, cat, cp, …, а также другие системные инструменты (вроде библиотек,
демонов, подсистем), на которые, в свою очередь, полагаются пользовательские приложения.

Unix-like окружения были настолько различны, что в определенный момент были разработаны
спецификации LSB (Linux Standard Base)
для того, чтобы прикладные программы могли работать на всех LSB-совместимых дистрибутивах.

С точки зрения ядра, на работающей машине все программные компоненты делятся на kernel space
(относящиеся непосредственно к Linux-ядру: модули ядра, его подсистемы, драйвера устройств)
и user space (все, кроме ядра, использующее его интерфейсы: демоны, пользовательские
программы и другие процессы).

Сборка минимального Linux-ядра вручную

Ядро Linux – монолитное, модульное и очень гибкое. Перед сборкой ядра, необходимо выполнить
его конфигурацию. За конфигурацию отвечает инструмент Kconfig, входящий в дерево исходников
ядра. Есть множество опций и инструментов конфигурации:

  • Путем непосредственного редактирования файла конфигурации .config. Это удобно для
    применения внешних инструментов сборки, переноса конфигов, их сравнения между собой и т.д.
  • Используя конфигурацию «по-умолчанию», поставляемую с релизом ядра. Такая конфигурация
    содержит умеренно-резонные значения и является хорошей стартовой точкой для дальнейшей
    кастомизации (например, создателями дистрибутивов). Осуществляется посредством
    make defconfig.
  • Используя старый файл конфигурации .config. Полезно, если при обновлении ядра на более
    новую версию, необходимо сохранить старые значения тех параметров, которые не изменились.
    Осуществляется посредством make oldconfig.
  • Используя псевдографический интерфейс настройки. У ядра много, очень много
    параметров настройки. Поэтому, удобно делать это именно в псевдографическом или графическом
    интерфейсе. В нем все настройки ядра структурированы и разбиты по категориям. Можно легко
    получить справку по тому или иному пункту конфигурации, выполнить поиск, отследить зависимости
    определенного пункта конфигурации от других пунктов. Осуществляется посредством
    make menuconfig (или make nconfig – менее предпочтительно).
    Является наиболее распространенным способом конфигурации вручную.
  • Используя полноценный графический интерфейс. Осуществляется посредством make xconfig
    или make gconfig. На деле псевдографический интерфейс оказывается удобнее графического.
  • Используя наиболее «легковесную» конфигурацию. Осуществляется посредством
    make tinyconfig. Настраивает ядро таким образом, чтобы оно было как можно более легким
    (содержало как можно меньше включенных компонентов и имело как можно меньший футпринт
    в оперативной памяти).

Другие опции конфигурации можно посмотреть, вызвав make help. Там же будут выведены и
остальные команды системы сборки ядра Kbuild.

В любом случае, конфигурация является файлом .config, который порождает тот или иной способ
конфигурации. Поэтому, можно например сперва выполнить make tinyconfig для получения
наиболее легковесного ядра, а затем выполнить make menuconfig и тонко подстроить ядро,
отталкиваясь от tinyconfig конфигурации.

Процедура:

  1. Убедиться, что установлены все необходимые пакеты.

  2. Перейти на kernel.org и выкачать tarball последней stable (не EOL)
    ветки ядра.

  3. Распаковать его в директорию, в которой мы еще некоторое время будем вести разработку:

    ## create directory 'kernel' in /home/user/
    mkdir -p ~/kernel
    ## extract .tar.xz kernel image to ~/kernel/
    tar -xJvf linux-*.tar.xz -C ~/kernel/
  4. Сконфигурировать ядро. Включение опций множества компонентов (например, включение сборки
    большого числа доступных модулей ядра) существенно увеличивают время его компиляции. Поэтому,
    будем использовать основанную на «ванильной» (vanilla — дефолтной) конфигурацию ядра, которая
    является относительно легковесной и, в то же время, не является слишком урезанной.

    Выполните:

    1. make distclean – в случае, если необходимо вернуть дерево исходников ядра в первозданное
      состояние (например, удалить все сгенерированные файлы, в том числе конфигурации, а также
      удалить патчи).
    2. make defconfig. Это породит vanilla-конфигурацию .config. Ее возьмем за основу.
    1. make menuconfig. Отредактируем полученный конфиг.

    Необходимо установить следующие опции:

    • [*] 64-bit kernel – yes.
    • General Setup --->
      • Preemption Model ---> Voluntary Kernel Preemption (Desktop)
      • [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support – yes
      • Configure standard kernel features (expert users)
        • [*] Enable support for printk – yes
        • [*] BUG() support – yes
    • Executable file formats --->
      • [*] Kernel support for ELF binaries – yes
      • [*] Kernel support for scripts starting with #! – yes
    • Device drivers --->
      • Generic Driver Options --->
        • [*] Maintain a devtmpfs filesystem to mount at /dev – yes
        • [*] Automount devtmpfs at /dev, after the kernel mounted the rootfs – yes
      • Character devices --->
        • [*] Enable TTY – yes
        • Serial drivers --->
          • [*] 8250/16550 and compatible serial support – yes
          • [*] Console on 8250/16550 and compatible serial port – yes
    • File systems --->
      • Pseudo filesystems --->
        • [*] /proc file system support – yes
        • [*] sysfs file system support – yes
    • Kernel hacking --->
      • Compile-time checks and compiler option --->
        • [*] Compile the kernel with debug info – yes
        • [*] Provide GDB scripts for kernel debugging – yes

    Затем сохранить конфиг с именем по-умолчанию .config и выйти из menuconfig

  5. Запустить сборку ядра.

    Для сборки ядра и модулей необходимо выполнить make -jN, где N – количество
    доступных ядер + 1 (для параллельной компиляции и, следовательно, ее ускорения).
    Посмотреть количество ядер можно, выполнив nproc. Например, для 4 ядер:
    make -j5.

    На данном этапе нам не требуется сборка модулей ядра, поэтому время компиляции
    можно сократить, собрав только ядро, не запуская сборку модулей.
    Выполните: make -jN vmlinux – это соберет только ядро.
    Время сборки составляет примерно 40 минут на моей машине.

    (если хотите попробовать быстрее – используйте make allnoconfig
    на предыдущем этапе, но ядро будет очень сильно урезанным).

    В результате появится собранный бинарник (образ) ядра vmlinux.
    Давайте оценим его размер:

    ls -hal ./
    
    # result:
    # -rwxr-xr-x   1 thd users 142M Sep  2 20:49 vmlinux

    Таким образом, бинарный образ ядра в данной конфигурации занимает 142 МиБ.
    Исторически, не все архитектуры могли хранить и загружать ядро такого размера,
    поэтому появился сжатый формат bzImage (и аналоги). Более подробно об этом
    можно почитать тут.

    Собранный vmlinux можно грузить с помощью QEMU напрямую, но давайте
    создадим также сжатый образ bzImage. Выполните:

    # remember that N should be replaced as described above
    make -jN bzImage

    В конце сборки, вы увидите:

    Setup is 13820 bytes (padded to 13824 bytes).
    System is 2331 kB
    CRC fa499bd
    Kernel: arch/x86/boot/bzImage is ready  (#1)
    

    Оценим размер теперь:

    ls -hal ./arch/x86/boot/bzImage
    
    # result:
    # -rw-r--r-- 1 thd users 2.3M Sep  2 20:50 ./arch/x86/boot/bzImage

    2.3 МиБ выглядит куда лучше. Особенно это полезно для embedded-систем, где
    ресурсы ограничены.

  6. Можно создать символические ссылки на собранные образы для удобства, положив вне дерева исходников ядра:

    ln -s ~/kernel/linux-*/vmlinux ~/kernel/vmlinux
    ln -s ~/kernel/linux-*/arch/x86/boot/bzImage ~/kernel/bzImage

    (будьте аккуратны с путями, символическую ссылку можно создать и на несуществующий объект)

  7. Загрузить собранный образ (лежит в arch/x86/boot/bzImage, или по созданному вами симлинку bzImage),
    используя QEMU:

    qemu-system-x86_64 -enable-kvm -m 256M -smp 4 -kernel "arch/x86_64/boot/bzImage" -append "console=ttyS0"

    Аргумент -m 256M указывает, что небходимо выделить 256 МиБ эмулируемой оперативной памяти
    (по-умолчанию выделяется 128 МиБ).

    Аргумент -smp 4 говорит о том, что эмулируемой системе будет видно до 4 ядер хост-системы

    В результате можно увидеть сообщение:

    Kernel panic — not syncing: No working init found. Try passing init= option to kernel.

    На данном этапе это нормально. Мы создадим initrd далее.

Примечания:

  • Использовать отдельное окно QEMU с эмуляцией графического видеоадаптера неудобно.
    Намного лучше подключить вывод консоли ядра непосредственно в терминал.
    Для этого мы собирали ядро с поддержкой serial-интерфейса, который умеет эмулировать QEMU
    и который можно перенаправить в терминал. Для этого, QEMU необходимо запустить с аргументами
    -nographic и -append "console=ttyS0" (передает Linux-ядру параметр, говорящий, что
    консоль необходимо подключить к последовательному порту 0):

    qemu-system-x86_64 -enable-kvm -m 256M -smp 4 -kernel "./bzImage" -append "console=ttyS0" -nographic
  • Включение KVM позволяет ускорить виртуализацию и практически приблизить ее (> 90%) к
    производительности выполнения на физической машине. Тем не менее, на некоторых машинах
    (в основном, виртуальных), KVM недоступен. Поэтому, можно запускать без аргумента
    -enable-kvm или использовать аргумент -no-kvm:

    qemu-system-x86_64 -no-kvm -m 256M -smp 4 -kernel "./bzImage" -append "console=ttyS0" -nographic
  • Для выхода из QEMU можно использовать комбинацию < Ctrl + a > < x > (сначала < Ctrl + a >, затем < x >).
    Полный список комбинаций доступен по < Ctrl + a > < h >

Сборка минимального Linux-окружения с использованием BusyBox. Initramfs

На предыдущем этапе, мы запустили собранное ядро в QEMU и обнаружили сообщение ошибки об отсутствующем init.
Это связано с тем, что ядро по окончании собственной инициализации, запускает процесс init и передает ему
управление. Init должен далее выполнить все необходимые инициализации userland (например, примонтировать
диски, запустить демоны, настроить сеть и т.д.).

Каким образом будет происходить загрузка системы, если, скажем корневая файловая система (rootfs, она же корень /)
находится на каком-нибудь удаленном устройстве (например, сетевой диск), или же для дальнейшей работы системы необходимо
загрузить модуль ядра драйвера определенного устройства? Как в таком случае будет происходить загрузка?

Для решения этих проблем существует механизм initramfs. При загрузке ядра, загрузчик может передать ядру некий минимальный
образ файловой системы, который будет содержать все необходимое для дальнейшей загрузки (например, модули ядра устройства или
утилиты, выполняющие необходимую конфигурацию для доступа к сетевым дискам). Образ initramfs (ramdisk) будет загружен в
оперативную память и подмонтирован в качестве файловой системы, откуда сможет выполниться /init и провести необходимые
инициализации. Обычно initramfs выполняет монтирование корневого раздела / (rootfs) и передает управление дальше.

Busybox — это гибкий и конфигурируемый минималистический набор базовых user space утилит (cp, ls, mv, sh, …),
реализованный в виде одного исполняемого файла. Это позволяет добиться крайне малого размера. Как устроен BusyBox?
При исполнении программы, она получает 0м аргументом имя, под которым была запущена. К примеру, ./myprog -flag somedir получит
нулевым аргументом ./myprog, а затем уже -flag и somedir. BusyBox использует эту особенность. Он представляет собой
один исполняемый файл, на который созданы символические ссылки (symlink) для каждой из утилит. Таким образом, при вызове отдельных
утилит, запускается на исполнение один и тот же файл, но получает при этом разный 0й аргумент и, взависимости от его значения,
реализует конкретный функционал той или иной утилиты.

Мы могли бы собрать BusyBox и поместить сразу в rootfs диска, который ядро сможет вычитать сразу. Например, эмулируемого QEMU
SATA-диска с GPT таблицей разделов и root-разделом в формате ext4. Для этого наверняка пришлось бы включить пару опций в
конфиге ядра (установить встраивание необходимых модулей SATA и EXT4 непосредственно в ядро, вместо сборки в качестве
отдельных подгружаемых модулей). Вполне допустимый вариант. Но мы пойдем другим путем и создадим initramfs с BusyBox.

Процедура создания ramdisk’a с BusyBox:

  1. Убедиться, что установлены все необходимые пакеты.

  2. Выгрузить BusyBox. Для расширения кругозора, будем использовать git-репозиторий.

    ## get tags of remote repo without cloning it, select latest
    git ls-remote --tags --sort="v:refname" git://busybox.net/busybox.git | tail -1
    ## for example, ``refs/tags/1_32_0`` means latest version tag is ``1_32_0``
    ## we will refer to it as TAG
    
    ## shallow clone BBox git repo with specified tag to ~/kernel/busybox
    git clone -b TAG --depth=1 git://busybox.net/busybox.git ~/kernel/busybox

    Или можно выкачать tarball последней релиз-версии с
    HTTP-ресурса релизов BusyBox.

  3. Интерфейс системы сборки BusyBox очень похож на систему сборки ядра. Настроим BusyBox.
    Выполните make menuconfig и установите:

    • Settings --->
      • [*] Build static binary (no shared libs) – yes

    Это включит сборку BusyBox в виде статически линкованного бинарника.

  4. Запустите сборку, выполнив make -jN, где N – количество
    доступных ядер + 1 (для параллельной компиляции и, следовательно, ее ускорения).

    Примечание:

    На момент написания, в библиотеке glibc происходит переход от устаревшего libcrypt
    к libxcrypt. В результате, на новых Arch-подобных системах с glibc версии
    больше 2.31-2, статическая библиотека libcrypt.a недоступна.

    Поэтому, при сборке BusyBox, в make необходмо явно передавать CRYPT_AVAILABLE=n,
    чтобы система сборки не пробовала линковаться с отсутствующей библиотекой.
    Например: make CRYPT_AVAILABLE=n -jN ....

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

    В дальнейшем мы рассмотрим системы автоматизированной сборки, которые, в том числе,
    собирают и необходимый тулчейн.

  5. Запустите формирование результирующей директории (в терминологии BusyBox, «установка»).

    # add CRYPT_AVAILABLE=n if needed
    make -jN install

    В итоге, в заданной на этапе конфигурации директорию установки (по умолчанию, _install
    внутри дерева исходников BusyBox) будет создан набор директорий; в /bin/busybox сброшен
    основной бинарник, а в других директориях – созданы символические ссылки на него для
    каждой из утилит. Далее буду использовать нотацию / как директории установки,
    поскольку она, в неком смысле, содержит дерево корневой системы.

  6. Перейдите в результирующую директорию BusyBox. В ней мы создадим несколько необходимых
    директорий для монтирования procfs и sysfs — /proc и /sys. Установим для них
    необходимые права доступа:

    cd _install
    mkdir proc
    mkdir sys
    # 555, what does it mean ??
    chmod 555 proc
    chmod 555 sys

    Создадим файл /init, который ядро выполнит сразу после окончания первоначальных
    этапов после запуска.

    Содержимое файла /init:

    #!/bin/sh
    
    mount -t proc none /proc
    mount -t sysfs none /sys
    
    # scans /sys for devices calling mknod to populate /dev
    mdev -s
    
    cat <<EOF
    
    ********* Boot took $(cut -d' ' -f1 /proc/uptime) s
    ********* Init done
    
    EOF
    
    exec /bin/sh

    Попробуйте объяснить, как работает этот скрипт.

    Поскольку /init должен быть исполняемым, необходимо установить флаг «executable»:

  7. Создадим образ с полученной корневой директорией BusyBox, который затем будем использовать
    в качестве initramfs при загрузке ядра. Из директории установки BusyBox (_install),
    выполните:

    find . -print0 | cpio --null --owner=0:0 -ov --format=newc | gzip -9 > ../../initramfs.cpio.gz

    В итоге, содержимое директории будет упаковано в cpio-архив, который затем будет сжат gzip и помещен
    двумя директориями выше (условно, в ~/kernel – если соблюдали предложенную структуру директорий)
    под именем initramfs.cpio.gz

  8. Попробуем загрузить собранное ранее ядро с только что созданным BusyBox образом initramfs:

    qemu-system-x86_64 -enable-kvm -m 256M -smp 4 -kernel "./bzImage" -initrd "./initramfs.cpio.gz" -append "console=ttyS0" -nographic

    Результат:

    [    2.003016] Freeing unused kernel image (text/rodata gap) memory: 2040K
    [    2.007398] Freeing unused kernel image (rodata/data gap) memory: 756K
    [    2.010724] Run /init as init process
    [    2.017627] mount (83) used greatest stack depth: 14568 bytes left
    
    ********* Boot took 2.03 s
    ********* Init done
    
    /bin/sh: can't access tty; job control turned off
    / #
    

    Таким образом, мы запустили ядро и получили минимальный shell и набор утилит из комплекта BusyBox

Сборка первого модуля ядра

Ядро Linux монолитное. Это значит, что, условно, все компоненты ядра лежат в одном адресном
пространстве и никак один от другого не изолированы. Такой подход делает ядро быстрым, поскольку
его компоненты могут легко обмениваться данными между собой. С этой особенностью связаны и
недостатки монолитных ядер: снижение надежности (один компонент при возникновении ошибки
может положить все ядро) и снижение безопасности (компоненты никак не изолированы друг
от друга и получение злоумышленником доступа к адресному пространству одного небезопасного
модуля ядра практически эквивалентно получению доступа ко всему ядру, а значит и
к любой составляющей работающей системы).

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

Микроядро, в отличии от монолитного, выполняет самый минимальный набор функций,
обеспечивая планирование и переключение задач (scheduling), а также межпроцессовую
коммуникацию (IPC) между основными компонентами ядра, такими как
файловая система, драйвера устройств, сетевой стек и другими (называются серверами
в терминологии микроядер). И каждый компонент микроядра является изолированным,
что дает большую надежность и безопасность.

Микроядра казались более современным вариантом в теории. Но в то время, когда микроядра
испытывали серьезные проблемы в разработке (коммуникация компонентов микроядра между
собой была очень медленной и неудобной), монолитное ядро Linux уже работало и работало
очень быстро. Его разработчики, в основном, Линюс приняли судьбоносное решение:
«Ядро должно быть быстрым и точка. Проблемы безопасности и надежности должны
решаться качественным, хорошо отлаженным кодом ядра. Если где-то в операционной
системе и есть место грязным хакам для достижения высокой производительности, то
это должно быть сделано в ядре. Чтобы пользовательские процессы уже работали в
чистом и надежном окружении».

Ядро Linux является модульным. Это значит, что компоненты ядра могут быть
динамически загружены и выгружены в рантайме примерно так, как плагины
в какой-нибудь программе. При загрузке модуля, ядро аллоцирует память,
выделяет для него ресурсы и прилинковывает модуль. Таким образом, модуль
как бы становится частью общего адресного пространства ядра. А при выгрузке
модуля – деаллоцирует и освобождает ресурсы. Если в работе модуля возникла
ошибка, ядро делает все возможное, чтобы восстановиться и продолжить работу,
за счет чего достигается более высокая отказоустойчивость, для этого
предусмотрено множество защитных механизмов. Но тем не менее,
в редких случаях, неправильно написанный модуль все равно может положить
всю систему и потребуется перезагрузка.

Пример модуля ядра

Рассмотрим и соберем простой модуль ядра, приведенный в директории demo/lab1
репозитория. Модуль выводит приветственное и прощальное сообщение, а какже текущее значение
jiffies.

firstmod.c :

#include <linux/module.h>    // required by all modules
#include <linux/kernel.h>    // required for sysinfo
#include <linux/init.h>    // used by module_init, module_exit macros
#include <linux/jiffies.h>   // where jiffies and its helpers reside

MODULE_DESCRIPTION("Basic module demo: init, deinit, printk, jiffies");
MODULE_AUTHOR("thodnev");
MODULE_VERSION("0.1");
MODULE_LICENSE("Dual MIT/GPL");              // this affects the kernel behavior

static int __init firstmod_init(void)
{
         printk(KERN_INFO "Hello, $username!njiffies = %lun", jiffies);
         return 0;
}

static void __exit firstmod_exit(void)
{
         printk(KERN_INFO "Long live the Kernel!n");
}

module_init(firstmod_init);
module_exit(firstmod_exit);

В общем случае, модули ядра как правило выполняются не линейно, а с использованием callback-функций.

Callback-функции – это одна из наиболее широко применяемых концепций асинхронного программирования.
Вы могли раньше видеть их, например: в Cube HAL STM32; в мире браузерного JS и серверного Node.JS;
при разработке графических интерфейсов. Приведу два примера:

  • Callback в GUI. Есть некая кнопка, по нажатию на которую должно происходить действие. Вместо того,
    чтобы постоянно проверять, была ли она нажата, разработчик регистрирует callback-функцию, которая
    будет выполнена как только пользователь нажмет эту кнопку;
  • Cube HAL STM32. Вам нужно отправлять данные по UART. Вместо того, чтобы после записи в
    transmit-буфер данных дожидаться успешной отправки предыдущего байта перед отправкой нового,
    можно использовать прерывания. Прерывание отправит порцию данных и вызовет callback-функцию.
    Разработчик может выполнить в ней все, что хотел осуществить по наступлению события
    «отправка завершена».
    Подробности реализации для UART на StackOverflow.

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

В примере выше есть две функции firstmod_init и firstmod_exit, которые регистрируются
как callback (с помощью module_init и module_exit) и будут вызываться, соответственно,
при загрузкеи выгрузке модуля ядра.

Callback’и должны быть быстрыми! Это главное правило. module_init -callback должен
быстро проинициализировать все необходимые для дальнейшей работы модуля ядра стуктуры данных,
установить callback-функции для реакции на нужные события, выполнить другие необходимые
инициализации и завершиться (функция, модуль ядра продолжает работу).
Если модулю ядра необходимо делать что-то в течение длительного
времени, это нужно делать, например запустив отдельный поток.

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

Сборка модулей ядра

Для сборки модулей используется система сборки ядра KBuild. Она в значительной мере
полагается на make. Чтобы собрать модуль, достаточно создать простейший Makefile:

 # (!) using paths with spaces may not work with Kbuild

 # this is what is used by Kbuild
 obj-m += firstmod.o

 # directory containing Makefile for kernel build
 KBUILDDIR ?= ../

.PHONY: modules tidy

# recur to the original kernel Makefile with some additions
modules:
              $(MAKE) -C "$(KBUILDDIR)" M="$(PWD)" modules

tidy:
              $(MAKE) -C "$(KBUILDDIR)" M="$(PWD)" clean

Здесь мы добавляем к переменной obj-m
(как бы append
к списку) название объектного файла модуля (.с заменили на .o), который хотим собрать.
Если модуль должен быть не отдельным файлом, подключаемым к ядру, а непосредственно
включен в образ ядра, то используется obj-y.

И далее вызываем make modules в директории с деревом исходников ядра, задавая
переменную
M как директорию,
где находится наш внешний (не входящий в само дерево исходников ядра) собираемый модуль.
Makefile ядра написан таким образом, что получая эту переменную, он перейдет в нужную
директорию и возьмет из Makefile, лежащего в ней, заданные переменные (нашу obj-m)
и начнет сборку. Более подробно о том, как работают Makefile ядра можно почитать
по ссылке, а о сборке
внешних модулей – тут.

Переменная KBUILDDIR является пользовательской, в ней в примере выше указана директория
с исходниками ядра. Если бы мы собирали отлаженный и уже готовый модуль ядра на
хост-системе, то KBUILDDIR можно было бы задать как
KBUILDDIR := /lib/modules/$(shell uname -r)/build/ (стандартная директория на рабочей
системе, где хранятся нужные для сборки модулей ядра части Kbuild). Если интересно, можете
попробовать сделать это для модуля выше (потребуются заголовки ядра, в Arch-подобных системах
это пакет linux-headers для той версии ядра, которая у вас запущена). Но мы будем
разрабатывать модули ядра в QEMU, чтобы не ложить систему и не перезагружаться каждый раз
при возникновении ошибки при написании кода.

В директории demo/lab1 лежит несколько измененный пример приведенного
выше Makefile, с минимальными отличиями. Для сборки модуля
ядра из приведенного примера выше, задайте KBUILDDIR равной директории, где
лежит дерево исходников ядра, использованное на предыдущих этапах. Это можно сделать
либо исправив переменную в самом Makefile, либо задав ее при вызове:

# assuming Makefile is changed
make modules

# or provide the kernel source directory when calling make
make KBUILDDIR="~/kernel/linux-5.8.7" modules

В результате сборки появится несколько файлов, главный из которых .ko – сам собранный
модуль. Попробуем загрузить его и рассмотрим его работу.

Работа с собранными модулями ядра в QEMU

До этого момента, наше окружение в QEMU было изолированным. Но теперь есть необходимость
пробросить собранный модуль в запущенную в QEMU систему. Проще всего пробросить в QEMU
директорию, в которой он был собран. Это можно сделать несколькими спсобами:

  • Подключить директорию с собранным модулем как сетевое хранилище. Это позволит читать
    и писать в нее прямо из работающей в QEMU системе. Такое хранилище будет работать
    довольно быстро, но потребует настройки сети как на хост-системе, так и на запущенной
    в QEMU гостевой;
  • Создать из директории с собранным модулем образ, который подключить как виртуальный
    жесткий диск в QEMU. В образ можно как читать, так и писать. Но работа с ним на
    хост-системе несколько неудобна. Этот способ больше подходит для создания образов
    целых дисков, нежели для шаринга директории;
  • Использовать VVFAT-диск в QEMU. QEMU содержит слой трансляции, позволяющий на лету
    создать из директории виртуальный жесткий диск. Это наиболее удобный способ для
    быстрого проброса директории в гостевую систему.
    Недостаток в том, что файловой системой виртуального диска будет устаревшая FAT,
    которая к тому же не умеет хранить полный набор прав доступа к файлам linux-систем.
    Диск можно будет читать (запись нестабильна), но для наших задач эти
    ограничения несущественны по сравнению с удобствами способа.

Используем последний способ.

  1. Запустим QEMU, пробросив в него директорию с собранным модулем ядра как VVFAT-диск.

    # here ./lab1 is our directory with built module
    qemu-system-x86_64 -enable-kvm -m 256M -smp 4 -kernel "./bzImage" -initrd "./initramfs.cpio.gz" 
                       -append "console=ttyS0" -nographic 
                       -drive file=fat:rw:./lab1,format=raw,media=disk
  2. Проверим, что диск распознался нашим ядром.

    ls -hal /dev
    
    # output
    # ....
    # brw-rw----    1 0        0           8,   0 Sep  2 05:59 sda
    # brw-rw----    1 0        0           8,   1 Sep  2 05:59 sda1
    # ....

    Видим, что в /dev присутствует два устройства: sda – наш диск (весь) и
    sda1 – его первый раздел в FAT32

  3. Создадим директорию /mnt и смонтируем в нее первый раздел диска.

    # create directory
    mkdir /mnt
    # you may also add mnt to initramfs to avoid creating it each time
    
    # mount qemu vvfat disk to it
    mount -t vfat /dev/sda1 /mnt
    
    # check that our files are present there
    ls -hal /mnt
    
    # output
    # ...
    # -rwxr-xr-x    1 0        0            937 Sep  2 10:09 Makefile
    # -rwxr-xr-x    1 0        0            698 Sep  2 09:55 firstmod.c
    # -rwxr-xr-x    1 0        0           4.5K Sep  2 10:20 firstmod.ko
    # ...
  4. Загрузим собранный модуль и проверим

    insmod /mnt/firstmod.ko
    
    # output
    # [  226.704469] firstmod: loading out-of-tree module taints kernel.
    # [  226.712031] Hello, $username!
    # [  226.712031] jiffies = 4294893705

    Таким образом, мы увидели вывод в лог ядра, порождаемый модулем в его init -функции,
    которая выполняется при загрузке модуля.

  5. Проверим список загруженных модулей

    lsmod
    
    # output
    # firstmod 16384 0 - Live 0xffffffffc00df000 (O)

    И видим, что наш модуль загружен и работает

  6. Попробуем выгрузить модуль

    rmmod firstmod
    
    # output
    # [  303.376667] Long live the Kernel!
    
    
    # check loaded modules again
    lsmod
    # output is empty – module was unloaded

    Мы выгрузили модуль. Функция exit модуля вывела в лог ядра сообщение.

Некоторые аспекты разработки модулей ядра

  • Мы видели лог ядра по ходу выполнения. Он был смешан с выводом stdout, поскольку оба направлены в одно
    устройство. Отдельно посмотреть лог ядра можно, выполнив dmesg:

    где tail -10 – вывод 10 последних строк из stdin (знак | после dmesg означает, что его stdout
    необходимо передать на stdin команды справа). Мы используем tail, поскольку лог ядра длинный.

  • При загрузке модулю можно передавать аргументы. Более подробно в man insmod на хост-системе
    и в документации LKMPG.

  • В общем случае, модули могут зависеть друг от друга и должны загружаться в порядке, разрешающем
    зависимости между ними. Например, так делает modprobe. Мы пока не затрагиваем этих аспектов
    и будем загружать и выгружать модули вручную.

  • Информацию о модуле можно посмотреть, используя modinfo, передав файл модуля ядра:

    На данном этапе, у нас урезанная система в QEMU. Но вы можете собрать модуль из примера
    на гостевой системе (он ведь работает, мы это проверили), выполнив

    make KBUILDDIR="/lib/modules/$(uname -r)/build/" modules

    (не забывайте делать это в чистой директории сборки, и почистить ее после проверки)

    Пример вывода:

    [thd@aspire lab1]$ modinfo ./firstmod.ko
    filename:       /home/thd/Work/KPI_Embedded_Linux/kernel/lab1/./firstmod.ko
    license:        Dual MIT/GPL
    version:        0.1
    author:         thodnev
    description:    Basic module demo: init, deinit, printk, jiffies
    srcversion:     E36B5885188779177077932
    depends:
    retpoline:      Y
    name:           firstmod
    vermagic:       5.4.60-2-MANJARO SMP preempt mod_unload modversions
    

    Отсюда видно, зачем мы использовали в коде ядра макросы вроде MODULE_AUTHOR
    для определения служебных данных.

    А декларация MODULE_LICENSE вообще имеет особый смысл: если модуль имеет
    лицензию, несовместимую с GPL, то при его загрузке, ему не будут доступно множество
    функционала ядра; ядро при этом станет tainted (испорченным). Багрепорты с логами
    с такого tainted-ядра откажутся принимать. Таким образом обеспечивается политика
    мягкого принуждения к соблюдению политики open-source разработки ядра.

  • Разработка модулей отличается от разработки пользовательских приложений.
    Это связано с тем, что модули ядра работают в пространстве ядра, а не в
    user space. Некоторые особенности:

    • Модули ядра являются привелегированными по отношению к юзерспейс-приложениям.
      Бесконечный цикл или ожидание освобождения ресурса в цикле недопустимы и могут
      повесить всю систему (не многоядерную или без preemption, о котором поговорим
      позже) или существенно снизить ее производительность. Поэтому модули ядра
      должны быть быстрыми. Особое внимание уделяется возможности/невозможности
      deadlock в блокировках доступа к разделяемым ресурсам;

    • Модули ядра не содержат main() и не выполняются линейно. При подключении
      модуля, он линкуется к ядру и запускается функция инициализации модуля.
      Эта функция выполняет необходимые инициализации и завершается, а модуль
      продолжает работать
      ;

    • Модули ядра не используют стандартных библиотек и соответствующих вызовов.
      В случае особой необходимости, использовать все же можно
      (ядро имеет доступ ко всему), но такая необходимость возникает редко, и
      их использование в любом случае будет выглядеть иначе,
      чем в юзерспейс-приложениях;

    • В следствие предыдущего пункта printf (а также другие привычные
      библиотечные функции) недоступны. Но у них есть
      аналоги
      из кода ядра.

      Вместо printf чаще всего используют printk;

    • Модули ядра не могут
      так же просто
      работать с числами с плавающей запятой,
      как это делают user space приложения. Это связано с особенностями FPU.
      Когда работает юзерспейс-приложение, возможность задействовать FPU ему
      предоставляет ядро. При работе в kernel space, модуль должен
      самостоятельно обеспечить доступность FPU на время проведения операций.

      Чаще всего использование операций с плавающей запятой в коде ядра и модулей
      сводится к обрамлению блока кода kernel_fpu_begin() и kernel_fpu_end().
      Но это неэффективно и не портабельно (Linux ядро ведь может работать и
      на архитектурах без FPU).

      Разработчики стараются всеми возможными способами избежать задержек, связанных
      с использованием FPU. Например, при помощи fixed point арифметики. Также модуль
      ядра может использовать `софтверную реализацию floating-point операций;

    • Модули ядра не освобождают ресурсы автоматически. Все выделенные ресурсы
      должны освобождаться вручную. При этом, важно предусмотреть нормальную
      отработку ситуаций, когда на этапе выделения определенного ресурса
      возникает ошибка;

    • Модули ядра могут прерываться, и делают это в разы чаще многопоточных
      userspace-приложений. При написании кода следует уделять этому особое
      внимание, задумываясь, а что будет, если между двумя идущими друг
      за дружкой инструкциями произойдет передача управления
      .

Задание

  1. Выполните приведенные выше шаги;

  2. Настало время начинать разбираться с особенностями базовых
    структур ядра и их организации:

    • Изучите документацию по средствам вывода сообщений
      (printk и более современные аналоги), а также по
      формат-строке printk и аналогов.
      Попробуйте использовать их для логгинга с разным уровнем важности;
    • В примере модуля был использован jiffies. Изучите раздел LKD
      об устройстве jiffies, а также раздел Linux Insides.
      Дополнительно можно заглянуть в исходники include/linux/jiffies.h
      и kernel/time/jiffies.c.
  3. Изучите раздел LKMPG о макросах __init и __exit,
    а также о передаче параметров модулю при загрузке;

  4. Измените модуль ядра firstmod.c из примера выше,
    чтобы вместо $username, выводилось имя, переданное в качестве параметра
    модуля при его подключении с помощью insmod, а если параметр
    не задан – использовалось то же $username по-умолчанию и в лог
    ядра выводилось сообщения с уровнем логгинга WARNING, что имя не задано.

    Примечание:

    Параметр обязательно должен иметь описание MODULE_PARM_DESC

    Дополните exit-callback модуля, чтобы при завершении модуль также
    печатал в лог текущее значение jiffies и время в секундах, которое
    прошло от init до exit (рассчитать из jiffies, используя
    jiffies_delta_to_msecs
    или jiffies_to_timespec64).

    О том, что такое timespec64 можно почитать в include/linux/time64.h.

  5. Оформите протокол (в ReStructured Text или MarkDown), в котором необходимо описать:

    • краткую суть задания;
    • что было сделано, поэтапно и кратко;
    • полученные результаты и их интерпретация (почему, о чем говорит и т.д.);
    • какие выводы из этого следуют;
  6. Выложите код модуля и Makefile для его сборки, а также протокол в виде
    Pull Request в GitHub-репозиторий курса
    (https://github.com/kpi-keoa/kpi-embedded-linux-course).

    Исходник модуля назовите по-своему. Это уже не первый модуль firstmod.
    Также большая просьба не указывать в KBUILDDIR выкладываемого в репозиторий
    Makefile свою конкретную директорию с исходниками ядра. Указывайте относительный
    путь, что-то нейтральное вроде ../linux-5.8.7

  7. На защите будьте готовы отвечать на вопросы по использованным командам,
    особенностям вашего кода и кода из примеров, Makefile, а также основным
    изложенным здесь концепциям

ЛР2: Средства отложенной работы: таймеры и тасклеты

В предыдущей лабораторной мы рассмотрели базовую структуру модуля ядра и простейший механизм учета времени – jiffies.
В данной работе мы познакомимся с двумя механизмами выволнения отложенной работы – таймерами и тасклетами, а также
затронем механизмы динамической аллокации памяти в ядре.

Задание

  1. Почитайте об аллокации памяти в ядре. Нас интересует kmalloc() (kzalloc() и другие), а также kfree().
    В то же время, vmalloc() пока не затрагиваем:

    • Раздел 8. Allocating Memory LDD
      (Linux Device Drivers by J. Corbet, A. Rubini, and G. Kroah-Hartman, 3rd ed., 2005).
    • Документ Memory Allocation Guide из Core API.
      В нем есть активные ссылки, по которым можно почитать описание соответствующих функций
    • Описания функций slab-аллокатора:
      The Slab Cache из Memory Management API.
    • О GFP-флагах и их комбинациях:
      Memory Allocation Controls из Memory Management API

    Убедитесь, что вам понятно, например, когда стоит использовать флаг GFP_KERNEL, а когда – его комбинацию с GFP_ATOMIC.

    Дополнительно, помимо LDD, о механизмах аллокации памяти можно почитать в
    разделе 12. Memory Management LKD
    (Linux Kernel Development by R. Love. , 3rd ed., 2010).

    Эти две книги доступны на Google Drive курса.

  2. Почитайте о таймерах:

    • Kernel Timers раздела 7. Time, Delays, and Deferred Work LDD.
      О небольших изменениях в API таймеров с момента выхода LDD можно почитать в статье
      Improving the kernel timers API на LWN.
    • Timers раздела 11. Timers and Time Management LKD.
    • Найдите описание соответствующих функций в подразделе Delaying, scheduling, and timer routines
      документации Driver API.
    • При необходимости, обратитесь к исходному коду заголовка
      linux/timer.h и его имплементации
      kernel/time/timer.c.
  3. Почитайте о тасклетах:

    • Tasklets раздела 7. Time, Delays, and Deferred Work LDD.
    • Краткое описание Softirqs and Tasklets
      , а также подраздел Tasklets
      раздела 8. Bottom Halves and Deferring Work LKD.
  4. Напишите модуль ядра, который:

    • Принимает два параметра, cnt и delay:

      • cnt является количеством циклов, которые должен отработать таймер;
      • delay является задержкой между двумя срабатываниями таймера.
      Примечание:

      Модуль должен отрабатывать при cnt и delay равных нулю.

    • В начале init печатает текущее значение jiffies в лог ядра.

    • Затем запускает тасклет, который должен напечатать свое значение jiffies в лог ядра.

      Примечание:

      В формат-строках указывайте, кто печатает в лог, чтобы не запутаться

    • Затем выделяет массив размера cnt, используя динамическую аллокацию.
      В этот массив таймер будет складывать значения.

      Примечание:

      kmalloc(), kzalloc() и аналоги возвращают void *.
      Стандарт С гарантирует, что void * может быть приведен
      к любому типу указателя автоматически. Поэтому, результат
      этих функций никогда не нужно кастовать к типу указателя.

      Аллокации памяти должны быть замкнуты на имя переменной, а не на тип.
      Поэтому, вместо sizeof(int), нужно использовать sizeof(*myvar).

      Более подробно это описано в README.rst репозитория.

    • Затем init запускает таймер с задержкой delay и функция завершается.

      При срабатывании, таймер кладет текущее значение jiffies в массив и
      перезапускается с задержкой delay. Общее количество раз, которые
      запускается таймер, равно cnt

    • В exit, модуль должен напечатать текущее значение jiffies,
      а также вывести все значения из массива.

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

    Примечание:

    Помните, что простые (basic) таймеры и тасклеты реализованы через softirq.
    Это значит, что они работают в атомарном контексте. В следствие этого,
    они не могут спать, запускать планирование задач и должны быть быстрыми.

    Если простой таймер или тасклет вызывает API ядра, которое может спать
    (например, kmalloc() с неверными GFP-флагами), то вызов заставит ожидать события,
    (в этом примере, пока аллокатор не будет готов выделить память).
    В то же время, пока функция простого таймера или тасклета не завершиться,
    нужный компонент ядра не сможет получить управления. В результате, это
    эквивалентно deadlock (ведет себя как бесконечный цикл while (1);
    в вызывающей функции).
    Ничего хорошего из этого не выйдет.
    Планировщик ядра не получит управление до следующего тика, в результате
    выстрелят watchdog’и.

    Поэтому, в атомарном контексте нельзя спать. Например, при аллокациях
    памяти, необходимо использовать соответствующие флаги.

    Помните, что аллокатор может не найти свободной памяти в общем пуле для
    выделения в момент запуска из softirq и вернет ошибку. Если память все
    же необходимо выделить (например, чтобы не пропустить событие)
    – необходимо использовать экстренные (emergency) пулы зарезервированной
    памяти. Например, флаг GFP_ATOMIC.

    Об атомарности:

    Неплохо бы позаботится об атомарном доступе, как минимум, к переменной-счетчику,
    используемой в функции таймера. У нас довольно простой случай: один writer и один reader,
    поэтому, проблем с синхронизацией возникать не должно.
    Тем не мене, возможна ситуация, когда таймер еще не успел обновить значение счетчика,
    а exit уже был вызван. Поэтому, в exit необходимо сперва завершать (или дожидаться
    завершения) таймера, а лишь затем вычитывать значение счетчика.

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

    О механизмах синхронизации детально поговорим на следующих занятиях.

    Об обработке исключений:

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

    Деаллокации выполняются в порядке обратном аллокациям. Например:

    int status = 0;         // ok
    
    int *a = kzalloc(sizeof(*a), GFP_KERNEL);
    if (NULL == a) {
            status = -ENOMEM;
            goto final;             // << NOTICE ORDER HERE
    }
    
    if ((b = kzalloc(sizeof(*b), GFP_KERNEL)) == NULL) {  // spartan style
            status = -ENOMEM;
            goto dealloc_a;         // << NOTICE ORDER HERE
    }
    
    unsigned char *arr = kmalloc(100 * sizeof(*arr), GFP_KERNEL);
    if (NULL == arr) {  // spartan style
            status = -ENOMEM;
            goto dealloc_b;         // << NOTICE ORDER HERE
    }
    
    process(a, b, arr);
    
    
    dealloc_arr:
            kfree(arr);
    dealloc_b:
            kfree(b);
    dealloc_a:
            kfree(a);
    final:
    
    return status;

    В качестве альтернативы (особенно, если аллокация и деаллокация выполняются в разных фукнциях,
    как init() и exit() модуля), можно завести структуру – битовое поле (bit field),
    где каждый бит будет устанавливатья в 1 после успешной аллокации.
    И функцию, которая будет сканировать биты в обратном порядке, выполняя деаллокацию.

  5. Оформите протокол (в ReStructured Text или MarkDown).
    В протоколе необходимо описать

    • краткую суть задания;
    • что было сделано, поэтапно и кратко;
    • полученные результаты и их интерпретация (почему, о чем говорит и т.д.);
    • какие выводы из этого следуют;

    а также, в протоколе необходимо дать ответ с кратким объяснением на следующие вопросы:

    • Почему jiffies, печатаемое в init и jiffies, выводимое тасклетом могут
      отличаться на 0, 1 или 2? Объясните для каждого случая.
    • Какие GFP-флаги использовались и почему именно они?
    • Чему равна разница jiffies между двумя запусками таймера и почему?
    • Что произойдет при задании параметра delay равным нулю?
  6. Выложите код модуля и Makefile для его сборки, а также протокол в виде
    Pull Request в GitHub-репозиторий курса
    (https://github.com/kpi-keoa/kpi-embedded-linux-course)

Понравилась статья? Поделить с друзьями:
  • Как написать модуль числа на питоне
  • Как написать модуль пайтон
  • Как написать модуль на паскале
  • Как написать модуль для нри
  • Как написать модуль для битрикс