Linux character devices что это

Драйверы устройств в Linux

Часть 5: Файлы символьных устройств – создание файлов и операции с ними

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

В моей предыдущей статье я упоминал, что даже при регистрации диапазона устройств , файлы устройств в директории /dev не создаются — Светлана должна была создать их вручную с помощью команды mknod . Но при дальнейшем изучении Светлана выяснила, что файлы устройств можно создавать автоматически с помощью демона udev . Она также узнала о втором шаге подключения файла устройства к драйверу устройства — связывание операций над файлом устройства с функциями драйвера устройства. Вот что она узнала.

Автоматическое создание файлов устройств

Ранее, в ядре 2.4, автоматическое создание файлов устройств выполнялось самим ядром в devfs с помощью вызова соответствующего API. Однако, по мере того, как ядро развивалось, разработчики ядра поняли, что файлы устройств больше связаны с пользовательским пространством и, следовательно, они должны быть именно там, а не в ядре. Исходя из этого принципа, теперь для рассматриваемого устройства в ядре в /sys только заполняется соответствующая информация о классе устройства и об устройстве. Затем в пользовательском пространстве эту информацию необходимо проинтерпретировать и выполнить соответствующее действие. В большинстве настольных систем Linux эту информацию собирает демон udev, и создает, соответственно, файлы устройств.

Демон udev можно с помощью его конфигурационных файлов настроить дополнительно и точно указать имена файлов устройств, права доступа к ним, их типы и т. д. Так что касается драйвера, требуется с помощью API моделей устройств Linux, объявленных в
, заполнить в /sys соответствующие записи. Все остальное делается с помощью udev . Класс устройства создается следующим образом:

Затем в этот класс информация об устройстве ( ) заносится следующим образом:

Здесь, в качестве first указывается dev_t . Соответственно, дополняющими или обратными вызовами, которые должны вызыватся в хронологически обратном порядке, являются:

Посмотрите на рис.1 на записи /sys , созданные с помощью chardrv — запись ( ) и с помощью mynull — запись ( ). Здесь также показан файл устройства, созданный с помощью udev по записи : , находящейся в файле dev .

Рис.1: Автоматическое создание файла устройства

В случае, если указаны несколько младших номеров minor, API device_create() и device_destroy() могут вызываться в цикле и в этом случае окажется полезной строка ( ). Например, вызов функции device_create() в цикле с использованием индекса i будет иметь следующий вид:

Операции с файлами

Независимо от того, что системные вызовы (или, в общем случае, операции с файлами), о которых мы рассказываем, применяются к обычным файлам, их также можно использовать и с файлами устройств. Т.е. мы можем сказать: если смотреть из пользовательского пространства, то в Linux почти все является файлами. Различие — в пространстве ядра, где виртуальная файловая система (VFS) определяет тип файла и пересылает файловые операции в соответствующий канал, например, в случае обычного файла или директория — в модуль файловой системы, или в соответствующий драйвер устройства в случае использования файла устройства. Мы будем рассматривать второй случай.

Теперь, чтобы VFS передала операции над файлом устройства в драйвер, ее следует об этом проинформировать. И это то, что называется регистрацией драйвером в VFS файловых операций. Регистрация состоит из двух этапов. (Код, указываемый в скобках, взят из кода «null -драйвера», который приведен ниже).

Во-первых, давайте занесем нужные нам файловые операции ( my_open , my_close , my_read , my_write , …) в структуру, описывающую файловые операции ( struct file_operations pugs_fops ) и ею инициализируем структуру, описывающую символьное устройство ( struct cdev c_dev ); используем для этого обращение cdev_init() .

Затем передадим эту структуру в VFS с помощью вызова cdev_add() . Обе операции cdev_init() и cdev_add() объявлены в
. Естественно, что также надо закодировать фактические операции с файлами ( my_open , my_close , my_read , my_write ).

Итак, для начала, давайте все это сделаем как можно проще — скажем, максимально просто в виде «null драйвера».

null — драйвер

Светлана повторила обычный процесс сборки, добавив при этом некоторые новые проверочные шаги, а именно:

  1. Собрала драйвер (файл .ko ) с помощью запуска команды make .
  2. Загрузила драйвер с помощью команды insmod .
  3. С помощью команды lsmod получила список всех загруженных модулей.
  4. С помощью команды cat /proc/devices . получила список используемых старших номеров major.
  5. Поэкспериментировала с «null драйвером» (подробности смотрите на рис.2).
  6. Выгрузила драйвер с помощью команды rmmod .

Рис.2: Эксперименты с «null драйвером»

Подведем итог

Светлана олпределенно была довольна; она сама написала символьный драйвер, который работает точно также, как и стандартный файл устройства /dev/null . Чтобы понять, что это значит, проверьте пару для файла /dev/null , а также выполните с ним команды echo и cat .

Но Светлану стала беспокоить одна особенность. В своем драйвере она использовала свои собственные вызовы ( my_open , my_close , my_read , my_write ), но, к удивлению, они, в отличие от любых других вызовов файловой системы, работают таким необычным образом. Что же тут необычного? Необычно, по крайней мере с точки зрения обычных файловых операций, то, что чтобы Светлана не записывала, при чтении она ничего не могла получить. Как она сможет решить эту проблему? Читайте следующую статью.

Источник

Anatomy of the Linux character devices

Character device is one of the class of Linux devices. The coordinative devices contain block devices, network devices. Every class of devices has its own support infrastructure by kernel, often called device driver model. This article will disscuss the simple character devices model.

First we need prepare a simple character device driver and user program using it.

The userspace program:

First install the ko, using dmesg we can the major and minor number of the device.

Then we using maknod to create an entry in /dev directory:

Now we have a chracter device, and run the main program, dmesg can show the open and read function has been executed.

Читайте также:  Видеоредактор для слабых пк linux

character device abstract

Linux kernel uses struct ‘cdev’ to represent charater devices.

The most import field hereis ‘struct file_operations’ which define the interface to virtual file system, when the user program trigger system call like open/read/write, it will finally go to the function which ops defines.

‘dev’ here represent the device number containing major and minor.

‘list’ links all of the character devices in the system. cdev’s initialization:

device number

Every device has a device number which was combined of major and minor number. Major number is used to indicate device driver major for indicate which device of the same class device.

‘dev_t’ is used to represent a device number, it is 32 unsigned bit.

Its’ high 12 bits represents major number and low 20 bits represents minor number

device number can be allocated by two function

The kernel uses ‘chrdevs’ global variable to manage device number’s allocation

‘register_chrdev_region’ records the device number in the chrdevs array.

The really work is done by ‘__register_chrdev_region’, which takes a major number and counts of the major. In this function, it insert the dev_t in the chrdevs’s entry. Of course we first need get the index:

Then ‘__register_chrdev_region’ check if the new added entry has conflicts with the already exists. If not added it in the chrdevs entry. After two 2 and 257 major number inserted:

‘alloc_chrdev_region’ is different with ‘register_chrdev_region’ is that the former hints the kernel to allocate a usable major number instead of specifying one in the later. It iterates chrdevs from last and find and empty entry to return as the major number.

character device registration

After initializing the char device and allocating the device number, we need register this char device to system. It is done by ‘cdev_add’ function.

Quite simple, the ‘p’ is the device which need added, the ‘dev’ is the device number, and count is the number of devices.

The core is to call kobj_map. ‘kobj_map’ adds the char device to a global variable ‘cdev_map’s hash table. ‘cdev_map’ is defined:

Here ‘probes’ field is liked the ‘chrdevs’ array, every entry represent a class of devices. The same value mod 255 is in the same entry.

‘kobj_map’ first allocates a probe and then insert to one of the ‘cdev_map’s probes entry. Below show after calling ‘cdev_add’ by two major satisfied major%255 = 2.

After calling ‘cdev_add’, the char device has been added to the system. The system can find our char device if needed. Before our user program can call user char device driver’s function, we need make a node in VFS so bridge the program and device driver.

make device file node

Device file is used to make a bridge between userspace program and kernel driver. As we know in Linux everything is a file, so if we want to export the driver’s service to user program, we must make an entry in VFS. We call mknod program in userspace will finally issues a ‘mknod’ system call. The the kernel will allocate an inode in the filesystem. For now, we will just consider the how to connect the VFS and char device driver and emit the VFS connect to the specific filesystem. The ‘vfs_mknod’ calls the specific filesystem’s mknod function.

We will uses shmem filesystem as an example, the inode operationgs is ‘shmem_dir_inode_operations’. So it calls ‘shmem_mknod’.

In ‘shmem_get_inode’, it allocates a new inode which represent our new create device, /dev/chr_dev for example. As our file is a char, it is special, so the ‘init_special_inode’ is called.

This function’s work is to set the inode’s field ‘i_fop’ and ‘i_rdev’. Char device’s ‘i_fop’ is set to ‘def_chr_fops’:

The VFS and device driver is connected by ‘inode->i_rdev’ now.

Char device’s operation

For now, the user program can open our device and issues system call like open/write/read.

do_sys_open –>do_filp_open –>path_openat –>do_last –>vfs_open –>do_dentry_open

After a long call chain, we arrive ‘do_dentry_open’ function:

For now the ‘inode’ is our create ‘/dev/chr_dev’ file. We assign ‘inode->i_fop’ to ‘f->f_op’. As we know:

So f->f_op = &def_chr_fops

Later, it will call f_op->open, which is ‘chrdev_open’:

‘kobj_lookup’ find the cdev in ‘cdev_map’ according the ‘i_rdev’. After succeeding find the cdev, filp’s ‘f_op’ will be replaced by our cdev’s ops which is the struct file_operations implemented in our char device driver.

Next it calls the open function in struct file_operations implemented in driver.

The above pic show the process of open a device file in user process.

Let’s look an example of how to use the fd returned by the open in close function.

finnally go to __fput

From above a can see, the kernel calls a lot of filp->f_op function, which is defined in the struct file_operations in char device driver.

© 2021 Terenceli with help from Jekyll Bootstrap and Twitter Bootstrap

Источник

Linux character devices что это

An ASR33 Teletype — origin of the abbreviation tty.

We meet several kinds of objects (character devices, tty drivers, line disciplines). Each registers itself at kernel initialization time (or module insertion time), and can afterwards be found when an open() is done.

11.1 Registration

A character device announces its existence by calling register_chrdev() . The call stores the given name (a string) and fops (a struct file_operations *) in the entry of the array chrdevs[] indexed by the integer major , the major device number of the device.

(Devices have a number, the device number , a combination of major and minor device number. Traditionally, the major device number gives the kind of device, and the minor device number is some kind of unit number. However, there are no rules — it is best to consider a device number a cookie, without known structure.)

This stored entry is used again when the device is opened: The filesystem recognizes that the file that is being opened is a special device file, and invokes init_special_inode() . This routine does Here to_kdev_t() converts the user mode version of the device number to the kernel version of the device number. The cdget() returns the struct char_device for this major number. It finds it using a hash table, and if we did not have it already, a new one is allocated. In all cases, the reference count of this struct is increased by one. The struct looks like Here hash is a link in the chain of devices with the same hash, count is the number of references — each cdget() increases and each cdput() decreases this by one, and if it becomes zero, the struct is removed from the hash chain and freed. The field dev stores the device number, the only thing we know about this device. The fields openers and sem are unused. Access to the hash table is protected by the cdev_lock spinlock.

Читайте также:  Как удалить realtek high definition audio driver windows 10

Finally the last item from init_special_inode() : That is, we cannot do anything with the character device except opening it, and when we do chrdev_open() is called.

On struct char_device

What is the use of this struct? After removing the unused fields openers and sem we see that we just have a struct in a hash chain and a reference count. It has no function at all, and all related code can be deleted (from 2.5.59).

11.2 Opening

The system call routine sys_open() calls filp_open() , and that calls dentry_open() , which does In other words, the file f_op is a copy of the inode i_fop . (This fops_get() returns its argument, but increments a reference count in case these file operations live in a module.) Finally, dentry_open() calls the inode open routine if there is one: Thus, it is here that chrdev_open() is called.

And the routine get_chrfops() retrieves the struct file operations * that was registered: (The actual routine checks whether the device did register already, and if not does a request_module(«char-major-N») first, where N is the major number.)

We see that the inode fops remains unchanged, so that its open still points to chrdev_open() , but the file fops is changed and now points to what the device registered.

11.3 The tty driver

Let us focus on /dev/tty1 , the first virtual console. Most code lives in drivers/char , in the files tty_io.c and n_tty.c and vt.c .

Registration

A tty driver announces its existence by calling tty_register_driver() . This call does a register_chrdev() (with tty_fops ) and hangs the driver in the chain tty_drivers .

That chain is used by get_tty_driver() , a routine that given a device number finds the tty driver that handles the device with that number.

get_tty_driver

This routine is used in two places: in fs/char_dev.c:get_chrfops() and in tty_io.c:init_dev() , called from tty_open . The latter use was expected, but what is this strange first use?

The idea here is that majors 4 and 5 (TTY_MAJOR and TTYAUX_MAJOR) may be served by several modules. Indeed, /dev/tty1 has major,minor 4,1 and is a virtual console, while /dev/ttyS1 has major,minor 4,65 and is a serial line. Thus, in drivers/serial/core.c:uart_register_driver() we see a call of tty_register_driver() , and this former routine is called, e.g., to register serial8250_reg , defined as while vt.c:vty_init() calls tty_register_driver() to register console_driver with major = TTY_MAJOR and minor_start = 1 .

Opening

As we saw above, opening a character device ends up with calling the open routine from the struct file_operations registered by the device. In the case of a tty, the open routine in tty_fops is tty_open .

The routine tty_open is long and messy, with a lot of special purpose code for controlling ttys, for pseudottys, etc. In the ordinary case the essential part is Thus, first of all, we create a tty_struct. Next, a pointer to this tty_struct is stored in the private_data field of the file struct, so that we can find it later, for example in tty_read() : Finally we call the open routine of the driver. The field tty->driver was set in init_dev() : Note that the entire struct tty_driver is copied in the assignment, so that individual fields can be changed without damaging the struct that was registered. However, this is never done, so having a copy is a waste of memory.

Line disciplines

The line discipline gives the protocol on the serial line. Each line discipline has a number, and the normal one is called N_TTY (0). Line disciplines are registered by tty_register_ldisc() , by storing a struct tty_ldisc in the array ldiscs[] (where the index is the line discipline number).

The normal discipline is registered by console_init() , as first among the registered disciplines:

The call we saw in init_dev() , does among other things

Thus, when tty->ldisc.open is called, it is the open field of the struct tty_ldisc_N_TTY . This struct lives in n_tty.c and its open field is n_tty_open .

More opening

After this preparation, finally tty->driver.open(tty,file) is called. Now that we had /dev/tty1 in mind, that is, one of the virtual consoles, let us see what routine this is. In vt.c:vty_init() we see So, our open routine is con_open() , an amusing open routine. It creates a virtual console if there wasn’t one. So, if you have 8 virtual consoles but open /dev/tty23 then you have 9.

If you have lots of unused consoles and want to free the memory they take, use the command deallocvt .

Exercise Which keystroke changes to console 23?

Reading

The system call sys_read() is found in fs/read_write.c . It calls vfs_read() , and this calls file->f_op->read() . In our case, this is the read routine of tty_fops , which unsurprisingly is tty_read . And above we saw that this calls tty->ldisc.read , which is the read field of tty_ldisc_N_TTY , called read_chan . The code is in n_tty.c . It downs the semaphore tty->atomic_read , hangs itself in the wait queue tty->read_wait of waiters for input, goes to sleep if no input is available, copies input to the user buffer, ups the semaphore tty->atomic_read and returns. (Reality is much more complicated. Try to read the code.)

So, hopefully, somebody will fill the input buffer. Who?

Читайте также:  Принтер hp color laserjet 1600 драйвер windows 10

Keyboard interrupts arrive at input/keyboard/atkbd.c:atkbd_interrupt() . It handles the keyboard protocol and converts scancode to keycode. Then input_report_key() is called, a define for input_event() , and this routine offers the event to all registered handlers.

Now keyboard.c:kbd_init() registers kbd_handler , and the result is that keyboard keystrokes will be handled by keyboard.c:kbd_event() , which calls kbd_keycode() . Here keyboard raw, mediumraw, xlate and unicode modes are handled, as is the magic sysrequest key. Scancodes have already been converted to keycodes, here we convert back (yecch) for raw mode, leave things for mediumraw mode, or further convert keycodes to characters using the keymap (set by the utility loadkeys ). Finally we call with the resulting bytes. Here vc is the foreground virtual console.

Now that is, put_queue() retrieves vc->vc_tty that was set by con_open() , and puts its stuff in the flip buffer. Then the work of transporting this to the read_buffer is scheduled. (In tty raw mode that is a plain copy, but in canonical mode we must react to special characters: the erase character erases, the interrupt character sends an interrupt, etc.) And when the transporting has been done, the bytes are ready to be read by a read() call.

Writing

Here things are entirely analogous. The system call sys_write() calls vfs_write() , and this calls file->f_op->write() . In our case, this is the write routine of tty_fops , which is tty_write . It does do_tty_write(tty->ldisc.write, . ) which downs the semaphore tty->atomic_write , possibly splits up the write into smaller chunks, calls its first argument and ups the semaphore again.

The write routine here is the write field of tty_ldisc_N_TTY , called write_chan . The code is in n_tty.c . It hangs itself in the wait queue tty->write_wait of waiters for room for output, tries to write by calling tty->driver.write , and if that fails to write everything goes to sleep.

Now our driver was console_driver with write routine con_write that calls do_con_write . Here very obscure things are done to handle escape sequences (cursor movement, screen colours, scrolling, etc. etc.), but in the normal case we see that actually writes the character and the (foreground / background / intensity) attributes. All very messy code — not a joy to behold.

11.4 Raw devices

Raw devices are character devices that can be bound to block devices. I/O from/to raw devices bypasses the block caches. Whether that is desirable depends on the application. Usually it is undesirable — there are all kinds of issues with raw devices. A main problem is that of coherency — the block device should not also be accessed directly. An annoyance is that I/O buffers must be aligned. Very few standard programs do this. The code for the raw device does set_blocksize() , so that bad things happen if the device was open already and using a different blocksize. Really, if raw is used it must be the only access path to the block device.

private_data

The block device belonging to a raw device is noted down in the private_data field of the file struct.

ioctls

There are two ioctls: RAW_SETBIND and RAW_GETBIND . The former connects a given raw device to a block device specified by major, minor. The latter reports on a connection. The file descriptor needed for the ioctl is that of the control raw device, with minor number zero. Unbinding is done by binding to major,minor = 0,0.

Binding is done by setting the i_mapping field of the raw device inode to the i_mapping field of the block device. After rebinding this will crash certain kernels because the inode for the block device may have gone away.

11.5 The random device

For security purposes Linux has the devices /dev/random and /dev/urandom . The former produces cryptographically strong bits, but may block when no entropy is available. The latter uses bits from the former when available, and a strong random generator otherwise, and does not block.

Exercise Try dd if=/dev/urandom of=/dev/null bs=1024 count=1000 and immediately afterwards dd if=/dev/random of=/dev/null bs=1024 count=1 . The former produces (more than) a megabyte of pseudorandom bits in less than a second. Probably this will have exhausted the entropy pool, and the latter will block until some randomness arrives. Move the mouse a little.

Randomness is needed in-kernel, e.g. for TCP sequence numbers — these must be hard to predict by an attacker to prevent spoofing -, and in user space for passwords or secret keys used to protect something — say the key for the .Xauthority file to protect access to the X server. The random character device is a standard part of the kernel, not something one selects with a config option.

The random device is a subdevice of the mem (for memory) device. The character device major 1 has subdevices mem , kmem , null , port , zero , full , random , urandom , kmsg (for minors 1,2,3,4,5,7,8,9,11 — long ago minor 6 was /dev/core , while minor 10 was reserved for /dev/aio but when aio was implemented it was done differently).

Thus, the registration is found in drivers/char/mem.c

Randomness is stored in the entropy_store , which has an associated variable entropy_count counting available random bits. The routine random_read() sees whether we have some bits, and if so returns them, and otherwise sleeps. The routine urandom_read() just extracts some bits.

So the question is how to obtain randomness. Something nobody can predict even when all running software is known. The random device uses four sources, namely the routines add_ X _randomness , for X = keyboard , mouse , disk , interrupt . The keyboard, and the mouse, and each IRQ, and each disk have an associated structure that remembers when we last did something, and the first and second order differences in the sequence of points in time. The routines add_keyboard_randomness() etc. call add_timer_randomness() , and the current time and the value contributed by the routine (keyboard scancode, mouse data, etc.) are mixed into the pool. In order to estimate the amount of entropy added, only the time is used, not the scancode (etc.) data.

Источник

Оцените статью