. Great!
Jun 15, 2017 - chardev.c: Creates a read-only char device that says how many times you've. * read from the dev file. * You can have some fun with this. May 3, 2012 - Character Devices are things like audio or graphics cards, or input. That is, reading or writing directly such as is done with redirection in the.
Welcome back. Now we can proceed with a more detailed exploration of the /dev directory. Device files Device files are also known as. Device files are employed to provide the operating system and users an interface to the devices that they represent.
All Linux device files are located in the /dev directory, which is an integral part of the root (/) filesystem because these device files must be available to the operating system during the boot process. One of the most important things to remember about these device files is that they are most definitely not device drivers. They are more accurately described as portals to the device drivers.
Data is passed from an application or the operating system to the device file which then passes it to the device driver which then sends it to the physical device. The reverse data path is also used, from the physical device through the device driver, the device file, and then to an application or another device. Let's look at the data flow of a typical command to visualize this.
Figure 1: Simple data flow for a typical command. In Figure 1, above, a simplified data flow is shown for a common command. Issuing the cat /etc/resolv.conf command from a GUI terminal emulator such as Konsole or xterm causes the resolv.conf file to be read from the disk with the disk device driver handling the device specific functions such as locating the file on the hard drive and reading it. The data is passed through the device file and then from the command to the device file and device driver for pseudo-terminal 6 where it is displayed in the terminal session. Of course, the output of the cat command could have been redirected to a file in the following manner, cat /etc/resolv.conf /etc/resolv.bak in order to create a backup of the file. In that case, the data flow on the left side of Figure 1 would remain the same while the data flow on the right would be through the /dev/sda2 device file, the hard drive device driver and then onto the hard drive itself.
These device files make it very easy to use standard streams (STD/IO) and redirection to access any and every device on a Linux or Unix computer. Simply directing a data stream to a device file sends the data to that device. Classification Device files can be classified in at least two ways. The first and most commonly used classification is that of the data stream commonly associated with the device.
For example, tty (teletype) and serial devices are considered to be character based because the data stream is transferred and handled one character or byte at a time. Block type devices such as hard drives transfer data in blocks, typically a multiple of 256 bytes. If you have not already, go ahead and as a non-root user in a terminal session, change the present working directory (PWD) to /dev and display a long listing. This shows a list of device files with their file permissions and their major and minor identification numbers. For example, the following device files are just a few of the ones in the /dev/directory on my Fedora 24 workstation. They represent disk and tty type devices. Notice the leftmost character of each line in the output.
The ones that have a 'b' are block type devices and the ones that begin with 'c' are character devices. Brw-rw- 1 root disk 8, 0 Nov 7 07:06 sda brw-rw- 1 root disk 8, 1 Nov 7 07:06 sda1 brw-rw- 1 root disk 8, 16 Nov 7 07:06 sdb brw-rw- 1 root disk 8, 17 Nov 7 07:06 sdb1 brw-rw- 1 root disk 8, 18 Nov 7 07:06 sdb2 crw-w- 1 root tty 4, 0 Nov 7 07:06 tty0 crw-w- 1 root tty 4, 1 Nov 7 07:07 tty1 crw-w- 1 root tty 4, 10 Nov 7 07:06 tty10 crw-w- 1 root tty 4, 11 Nov 7 07:06 tty11 The more detailed and explicit way to identify device files is using the device major and minor numbers. The disk devices have a major number of 8 which designates them as SCSI block devices. Note that all PATA and SATA hard drives have been managed by the SCSI subsystem because the old ATA subsystem was many years ago deemed as not maintainable due to the poor quality of its code. As a result, hard drives that would previously have been designated as 'hda-z' are now referred to as 'sda-z'. You can probably infer the pattern of disk drive minor numbers in the small sample shown above. Minor numbers 0, 16, 32 and so on up through 240 are the whole disk numbers.
So major/minor 8/16 represents the whole disk /dev/sdb and 8/17 is the device file for the first partition, /dev/sdb1. Numbers 8/34 would be /dev/sdc2. The tty device files in the list above are numbered a bit more simply from tty0 through tty63. The file at Kernel.org is the official registry of device types and major and minor number allocations. It can help you understand the major/minor numbers for all currently defined devices. Fun with device files Let's take a few minutes now and perform a couple fun experiments that will illustrate the power and flexibility of the Linux device files.
Most Linux distributions have multiple virtual consoles, 1 through 7, that can be used to login to a local console session with a shell interface. These can be accessed using the key combinations Ctrl-Alt-F1 for console 1, Ctrl-Alt-F2 for console 2, and so on. Press Ctrl-Alt-F2 to switch to console 2. On some distributions, the login information includes the tty device associated with this console, but many do not. It should be tty2 because you are in console 2.
Log in as a non-root user. Then you can use the who am i command—yes, just like that, with spaces—to determine which tty device is connected to this console. Before we actually perform this experiment, look at a listing of the tty2 and tty3 devices in /dev. Ls -l /dev /tty 23 There will be a large number of tty devices defined but we do not care about most of them, just the tty2 and tty3 devices.
As device files, there is nothing special about them; they are simply character type devices. We will use these devices for this experiment. The tty2 device is attached to virtual console 2 and the tty3 device is attached to virtual console 3. Press Ctrl-Alt-F3 to switch to console 3. Log in again as the same non-root user. Now enter the following command on console 3. Echo 'Hello world' /dev /tty2 Press Ctrl-Alt-F2 to return to console 2.
The string 'Hello world' (without quotes) is displayed in console 2. This experiment can also be performed with terminal emulators on the GUI desktop. Terminal sessions on the desktop use pseudo terminal devices in the /dev tree, such as /dev/pts/1. Open two terminal sessions using Konsole or Xterm.
Determine which pseudo-terminals they are connected to and use one to send a message to the other. Now continue the experiment by using the cat command to display the /etc/fstab file on a different terminal. Another interesting experiment is to print a file directly to the printer using the cat command. Assuming that your printer device is /dev/usb/lp0, and that your printer can print PDF files directly, the following command will print the PDF file test.pdf on your printer. Cat test.pdf /dev /usb /lp0 The /dev directory contains some very interesting device files that are portals to hardware that one does not normally think of as a device like a hard drive or display. For one example, system memory—RAM—is not something that is normally considered as a 'device,' yet /dev/mem is the portal through which direct access to memory can be achieved.
The following example had some interesting results. Dd if= /dev /mem bs= 2048 count= 100 The dd command above provides a bit more control than simply using the cat command to dump all of a system's memory. It provides the ability to specify how much data is read from /dev/mem and would also allow me to specify the point at which to start reading data from memory. Although some memory was read, the kernel responded with the following error that I found in /var/log/messages. Nov 14 14:37:31 david kernel: usercopy: kernel memory exposure attempt detected from ffff9f (dma-kmalloc-512) (2048 bytes) What this error means is that the kernel is doing its job by protecting memory that belongs to other processes which is exactly how it should work.
So, although you can use /dev/mem to display data stored in RAM memory, access to most memory space is protected and will result in errors. Only that virtual memory which is assigned by the kernel memory manager to the BASH shell running the dd command should be accessible without causing an error. Sorry, but you cannot snoop in memory that does not belong to you unless you find a vulnerability to exploit.
There are some other very interesting device files in /dev. The device files null, zero, random and urandom are not associated with any physical devices. For example, the null device /dev/null can be used as a target for the redirection of output from shell commands or programs so that they are not displayed on the terminal. I frequently use this in my BASH scripts to prevent users from being presented with output that might be confusing to them. The /dev/null device can be used to produce a string of null characters. Use the dd command as shown below to view some output from the /dev/null device file.
# dd if=/dev/null bs=512 count=500 od -c 0+0 records in 0+0 records out 0 bytes copied, 1.5885e-05 s, 0.0 kB/s 0000000 Note that there is really no visible output because null characters are nothing. Note the byte count. The /dev/random and /dev/urandom devices are also very interesting. As their names imply, they both produce random output—not just numbers but any and all byte combinations. The /dev/urandom device produces deterministic random output and is very fast. That means the output is determined by an algorithm and uses a seed string as a starting point. As a result it is possible, although very difficult, for a hacker to reproduce the output if the original seed is known.
Use the command cat /dev/urandom to view typical output. You can use Ctrl-c to break out. The /dev/random device file produces non-deterministic random output but it produces output more slowly. This output is not determined by an algorithm that is dependent upon the previous number, but is generated in response to keystrokes and mouse movements.
This method makes it far more difficult to duplicate a specific series of random numbers. Use the cat command to view some of the output from the /dev/random device file. Try moving the mouse to see how it affects the output. As its name implies, the /dev/zero device file produces an unending string of zeroes as output. Note that these are Octal zeroes and not the ASCII character zero (0).
Use the dd command as shown below to view some output from the /dev/zero device file. # dd if=/dev/zero bs=512 count=500 od -c 0000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.
500+0 records in 500+0 records out 256000 bytes (256 kB, 250 KiB) copied, 0.00126996 s, 202 MB/s 0764000 Note that the byte count for this command is non-zero. Creating device files In the past, the device files in /dev were all created at installation time, resulting in a directory full of almost every possible device file, even though most would never be used. In the unlikely event that a new device file was needed or one was accidentally deleted and needed to be re-created, the mknod program was available to manually create device files. All you had to know was the device major and minor numbers. CentOS and RHEL 6 and 7, as well as all versions of Fedora going back to at least as far Fedora 15, use the newer method of creating the device files. All device files are created at boot time. This functionality is possible because the udev device manager detects addition and removal of devices as they occur.
This allows for true dynamic plug-n-play functionality while the host is up and running. It also performs the same task at boot time by detecting all devices installed on the system very early in the boot process. Going back to your listing of the files in /dev, notice the date and time on the files.
![Linux Read Character Device Linux Read Character Device](/uploads/1/2/5/4/125495729/136541593.png)
All of them were created during the last boot. You can verify this using the uptime or last commands. In my device listing above, all of those files were created at 7:06 AM on November 7, which is the last time I booted the system. Of course, the mknod command is still available, but the new MAKEDEV (yes, in all uppercase—which in my opinion is contrary to the Linux philosophy of using all lowercase command names) command provides an easier interface for creating device files, should the need arise. The MAKEDEV command is not installed by default in current versions of Fedora or CentOS 7; it is installed in CentOS 6.
You can use YUM or DNF to install the MAKEDEV package. Conclusion Interestingly enough, it had been a long time since I needed to create a device file.
However, just recently I had an interesting situation where one of the device files I typically use was not created and I did have to create it. I have not had any problem with that device since. So a situation caused by a missing device file can still happen and knowing how to deal with it can be important. I have not covered many of the myriad different types of device files that you might encounter.
That information is available in plenty of detail in the resources cited. I hope I have given you some basic understanding of how these files function and the tools to allow you to explore more on your own.
Resources., David Both, Opensource.com., David Both, Opensource.com., The Linux Documentation Project., Wikipedia., Kernel.org. David Both - David Both is a Linux and Open Source advocate who resides in Raleigh, North Carolina. He has been in the IT industry for over forty years and taught OS/2 for IBM where he worked for over 20 years. While at IBM, he wrote the first training course for the original IBM PC in 1981. He has taught RHCE classes for Red Hat and has worked at MCI Worldcom, Cisco, and the State of North Carolina. He has been working with Linux and Open Source Software for almost 20 years.
David has written articles for. For more discussion on open source and the role of the CIO in the enterprise, join us at. The opinions expressed on this website are those of each author, not of the author's employer or of Red Hat. Opensource.com aspires to publish all content under a but may not be able to do so in all cases. You are responsible for ensuring that you have the necessary permission to reuse any work on this site. Red Hat and the Shadowman logo are trademarks of Red Hat, Inc., registered in the United States and other countries.
Chapter 3. Char Drivers The goal of this chapter is to write a complete char device driver. We develop a character driver because this class is suitable for most simple hardware devices. Char drivers are also easier to understand than block drivers or network drivers (which we get to in later chapters). Our ultimate aim is to write a modularized char driver, but we won't talk about modularization issues in this chapter. Throughout the chapter, we present code fragments extracted from a real device driver: scull (Simple Character Utility for Loading Localities).
Scull is a char driver that acts on a memory area as though it were a device. In this chapter, because of that peculiarity of scull, we use the word device interchangeably with 'the memory area used by scull.' The advantage of scull is that it isn't hardware dependent. Scull just acts on some memory, allocated from the kernel. Anyone can compile and run scull, and scull is portable across the computer architectures on which Linux runs. On the other hand, the device doesn't do anything 'useful' other than demonstrate the interface between the kernel and char drivers and allow the user to run some tests. The Design of scull The first step of driver writing is defining the capabilities (the mechanism) the driver will offer to user programs.
Since our 'device' is part of the computer's memory, we're free to do what we want with it. It can be a sequential or random-access device, one device or many, and so on. To make scull useful as a template for writing real drivers for real devices, we'll show you how to implement several device abstractions on top of the computer memory, each with a different personality. The scull source implements the following devices.
Each kind of device implemented by the module is referred to as a type. Scull0 to scull3 Four devices, each consisting of a memory area that is both global and persistent.
Global means that if the device is opened multiple times, the data contained within the device is shared by all the file descriptors that opened it. Persistent means that if the device is closed and reopened, data isn't lost. This device can be fun to work with, because it can be accessed and tested using conventional commands, such as cp, cat, and shell I/O redirection. Scullpipe0 to scullpipe3 Four FIFO (first-in-first-out) devices, which act like pipes. One process reads what another process writes.
If multiple processes read the same device, they contend for data. The internals of scullpipe will show how blocking and nonblocking read and write can be implemented without having to resort to interrupts. Although real drivers synchronize with their devices using hardware interrupts, the topic of blocking and nonblocking operations is an important one and is separate from interrupt handling (covered in ). Scullsingle scullpriv sculluid scullwuid These devices are similar to scull0 but with some limitations on when an open is permitted.
The first ( scullsingle) allows only one process at a time to use the driver, whereas scullpriv is private to each virtual console (or X terminal session), because processes on each console/terminal get different memory areas. Sculluid and scullwuid can be opened multiple times, but only by one user at a time; the former returns an error of 'Device Busy' if another user is locking the device, whereas the latter implements blocking open. These variations of scull would appear to be confusing policy and mechanism, but they are worth looking at, because some real-life devices require this sort of management. Each of the scull devices demonstrates different features of a driver and presents different difficulties. This chapter covers the internals of scull0 to scull3; the more advanced devices are covered in.
Scullpipe is described in the section and the others are described in. Major and Minor Numbers Char devices are accessed through names in the filesystem. Those names are called special files or device files or simply nodes of the filesystem tree; they are conventionally located in the /dev directory. Special files for char drivers are identified by a 'c' in the first column of the output of ls -l. Block devices appear in /dev as well, but they are identified by a 'b.' The focus of this chapter is on char devices, but much of the following information applies to block devices as well.
If you issue the ls -l command, you'll see two numbers (separated by a comma) in the device file entries before the date of the last modification, where the file length normally appears. These numbers are the major and minor device number for the particular device. The following listing shows a few devices as they appear on a typical system.
Their major numbers are 1, 4, 7, and 10, while the minors are 1, 3, 5, 64, 65, and 129. Crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null crw- 1 root root 10, 1 Apr 11 2002 psaux crw- 1 root root 4, 1 Oct 28 03:04 tty1 crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0 crw-rw- 1 root uucp 4, 65 Apr 11 2002 ttyS1 crw-w- 1 vcsa tty 7, 1 Apr 11 2002 vcs1 crw-w- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1 crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero Traditionally, the major number identifies the driver associated with the device. For example, /dev/null and /dev/zero are both managed by driver 1, whereas virtual consoles and serial terminals are managed by driver 4; similarly, both vcs1 and vcsa1 devices are managed by driver 7. Modern Linux kernels allow multiple drivers to share major numbers, but most devices that you will see are still organized on the one-major-one-driver principle. The minor number is used by the kernel to determine exactly which device is being referred to. Depending on how your driver is written (as we will see below), you can either get a direct pointer to your device from the kernel, or you can use the minor number yourself as an index into a local array of devices. Either way, the kernel itself knows almost nothing about minor numbers beyond the fact that they refer to devices implemented by your driver.
The Internal Representation of Device Numbers Within the kernel, the devt type (defined in ) is used to hold device numbers—both the major and minor parts. As of Version 2.6.0 of the kernel, devt is a 32-bit quantity with 12 bits set aside for the major number and 20 for the minor number. Your code should, of course, never make any assumptions about the internal organization of device numbers; it should, instead, make use of a set of macros found in. To obtain the major or minor parts of a devt, use: MAJOR(devt dev); MINOR(devt dev); If, instead, you have the major and minor numbers and need to turn them into a devt, use: MKDEV(int major, int minor); Note that the 2.6 kernel can accommodate a vast number of devices, while previous kernel versions were limited to 255 major and 255 minor numbers. One assumes that the wider range will be sufficient for quite some time, but the computing field is littered with erroneous assumptions of that nature. So you should expect that the format of devt could change again in the future; if you write your drivers carefully, however, these changes will not be a problem.
Allocating and Freeing Device Numbers One of the first things your driver will need to do when setting up a char device is to obtain one or more device numbers to work with. The necessary function for this task is registerchrdevregion, which is declared in: int registerchrdevregion(devt first, unsigned int count, char.name); Here, first is the beginning device number of the range you would like to allocate. The minor number portion of first is often 0, but there is no requirement to that effect. Count is the total number of contiguous device numbers you are requesting.
Note that, if count is large, the range you request could spill over to the next major number; but everything will still work properly as long as the number range you request is available. Finally, name is the name of the device that should be associated with this number range; it will appear in /proc/devices and sysfs. As with most kernel functions, the return value from registerchrdevregion will be 0 if the allocation was successfully performed.
In case of error, a negative error code will be returned, and you will not have access to the requested region. Registerchrdevregion works well if you know ahead of time exactly which device numbers you want. Often, however, you will not know which major numbers your device will use; there is a constant effort within the Linux kernel development community to move over to the use of dynamicly-allocated device numbers. The kernel will happily allocate a major number for you on the fly, but you must request this allocation by using a different function: int allocchrdevregion(devt.dev, unsigned int firstminor, unsigned int count, char.name); With this function, dev is an output-only parameter that will, on successful completion, hold the first number in your allocated range.
Firstminor should be the requested first minor number to use; it is usually 0. The count and name parameters work like those given to requestchrdevregion.
Regardless of how you allocate your device numbers, you should free them when they are no longer in use. Device numbers are freed with: void unregisterchrdevregion(devt first, unsigned int count); The usual place to call unregisterchrdevregion would be in your module's cleanup function. The above functions allocate device numbers for your driver's use, but they do not tell the kernel anything about what you will actually do with those numbers. Before a user-space program can access one of those device numbers, your driver needs to connect them to its internal functions that implement the device's operations. We will describe how this connection is accomplished shortly, but there are a couple of necessary digressions to take care of first.
Some Important Data Structures As you can imagine, device number registration is just the first of many tasks that driver code must carry out. We will soon look at other important driver components, but one other digression is needed first. Most of the fundamental driver operations involve three important kernel data structures, called fileoperations, file, and inode. A basic familiarity with these structures is required to be able to do much of anything interesting, so we will now take a quick look at each of them before getting into the details of how to implement the fundamental driver operations. File Operations So far, we have reserved som e device numbers for our use, but we have not yet connected any of our driver's operations to those numbers.
The fileoperations structure is how a char driver sets up this connection. The structure, defined in, is a collection of function pointers.
Each open file (represented internally by a file structure, which we will examine shortly) is associated with its own set of functions (by including a field called fop that points to a fileoperations structure). The operations are mostly in charge of implementing the system calls and are therefore, named open, read, and so on. We can consider the file to be an 'object' and the functions operating on it to be its 'methods,' using object-oriented programming terminology to denote actions declared by an object to act on itself. This is the first sign of object-oriented programming we see in the Linux kernel, and we'll see more in later chapters. Conventionally, a fileoperations structure or a pointer to one is called fops (or some variation thereof ). Each field in the structure must point to the function in the driver that implements a specific operation, or be left NULL for unsupported operations.
The exact behavior of the kernel when a NULL pointer is specified is different for each function, as the list later in this section shows. The following list introduces all the operations that an application can invoke on a device. We've tried to keep the list brief so it can be used as a reference, merely summarizing each operation and the default kernel behavior when a NULL pointer is used.
As you read through the list of fileoperations methods, you will note that a number of parameters include the string user. This annotation is a form of documentation, noting that a pointer is a user-space address that cannot be directly dereferenced.
For normal compilationuser has no effect, but it can be used by external checking software to find misuse of user-space addresses. The rest of the chapter, after describing some other important data structures, explains the role of the most important operations and offers hints, caveats, and real code examples. We defer discussion of the more complex operations to later chapters, because we aren't ready to dig into topics such as memory management, blocking operations, and asynchronous notification quite yet.
The file Structure struct file, defined in, is the second most important data structure used in device drivers. Note that a file has nothing to do with the FILE pointers of user-space programs. A FILE is defined in the C library and never appears in kernel code. A struct file, on the other hand, is a kernel structure that never appears in user programs. The file structure represents an open file.
(It is not specific to device drivers; every open file in the system has an associated struct file in kernel space.) It is created by the kernel on open and is passed to any function that operates on the file, until the last close. After all instances of the file are closed, the kernel releases the data structure.
In the kernel sources, a pointer to struct file is usually called either file or filp ('file pointer'). We'll consistently call the pointer filp to prevent ambiguities with the structure itself. Thus, file refers to the structure and filp to a pointer to the structure. The most important fields of struct file are shown here.
As in the previous section, the list can be skipped on a first reading. However, later in this chapter, when we face some real C code, we'll discuss the fields in more detail. Modet fmode; The file mode identifies the file as either readable or writable (or both), by means of the bits FMODEREAD and FMODEWRITE.
You might want to check this field for read/write permission in your open or ioctl function, but you don't need to check permissions for read and write, because the kernel checks before invoking your method. An attempt to read or write when the file has not been opened for that type of access is rejected without the driver even knowing about it. Lofft fpos; The current reading or writing position.
Lofft is a 64-bit value on all platforms ( long long in gcc terminology). The driver can read this value if it needs to know the current position in the file but should not normally change it; read and write should update a position using the pointer they receive as the last argument instead of acting on filp-fpos directly. The one exception to this rule is in the llseek method, the purpose of which is to change the file position. Unsigned int fflags; These are the file flags, such as ORDONLY, ONONBLOCK, and OSYNC. A driver should check the ONONBLOCK flag to see if nonblocking operation has been requested (we discuss nonblocking I/O in ); the other flags are seldom used.
In particular, read/write permission should be checked using fmode rather than fflags. All the flags are defined in the header. Struct fileoperations.fop; The operations associated with the file.
The kernel assigns the pointer as part of its implementation of open and then reads it when it needs to dispatch any operations. The value in filp-fop is never saved by the kernel for later reference; this means that you can change the file operations associated with your file, and the new methods will be effective after you return to the caller. For example, the code for open associated with major number 1 ( /dev/null, /dev/zero, and so on) substitutes the operations in filp-fop depending on the minor number being opened. This practice allows the implementation of several behaviors under the same major number without introducing overhead at each system call.
The ability to replace the file operations is the kernel equivalent of 'method overriding' in object-oriented programming. Void.privatedata; The open system call sets this pointer to NULL before calling the open method for the driver. You are free to make its own use of the field or to ignore it; you can use the field to point to allocated data, but then you must remember to free that memory in the release method before the file structure is destroyed by the kernel. Privatedata is a useful resource for preserving state information across system calls and is used by most of our sample modules.
Struct dentry.fdentry; The directory entry ( dentry) structure associated with the file. Device driver writers normally need not concern themselves with dentry structures, other than to access the inode structure as filp-fdentry-dinode. The real structure has a few more fields, but they aren't useful to device drivers.
We can safely ignore those fields, because drivers never create file structures; they only access structures created elsewhere. Devt irdev; For inodes that represent device files, this field contains the actual device number.
Struct cdev.icdev; struct cdev is the kernel's internal structure that represents char devices; this field contains a pointer to that structure when the inode refers to a char device file. The type of irdev changed over the course of the 2.5 development series, breaking a lot of drivers. As a way of encouraging more portable programming, the kernel developers have added two macros that can be used to obtain the major and minor number from an inode: unsigned int iminor(struct inode.inode); unsigned int imajor(struct inode.inode); In the interest of not being caught by the next change, these macros should be used instead of manipulating irdev directly. Char Device Registration As we mentioned, the kernel uses structures of type struct cdev to represent char devices internally. Before the kernel invokes your device's operations, you must allocate and register one or more of these structures.
To do so, your code should include, where the structure and its associated helper functions are defined. There are two ways of allocating and initializing one of these structures. If you wish to obtain a standalone cdev structure at runtime, you may do so with code such as: struct cdev.mycdev = cdevalloc( ); mycdev-ops = &myfops; Chances are, however, that you will want to embed the cdev structure within a device-specific structure of your own; that is what scull does. In that case, you should initialize the structure that you have already allocated with: void cdevinit(struct cdev.cdev, struct fileoperations.fops); Either way, there is one other struct cdev field that you need to initialize. Like the fileoperations structure, struct cdev has an owner field that should be set to THISMODULE. Once the cdev structure is set up, the final step is to tell the kernel about it with a call to: int cdevadd(struct cdev.dev, devt num, unsigned int count); Here, dev is the cdev structure, num is the first device number to which this device responds, and count is the number of device numbers that should be associated with the device. Often count is one, but there are situations where it makes sense to have more than one device number correspond to a specific device.
Consider, for example, the SCSI tape driver, which allows user space to select operating modes (such as density) by assigning multiple minor numbers to each physical device. There are a couple of important things to keep in mind when using cdevadd. The first is that this call can fail. If it returns a negative error code, your device has not been added to the system.
It almost always succeeds, however, and that brings up the other point: as soon as cdevadd returns, your device is 'live' and its operations can be called by the kernel. You should not call cdevadd until your driver is completely ready to handle operations on the device. To remove a char device from the system, call: void cdevdel(struct cdev.dev); Clearly, you should not access the cdev structure after passing it to cdevdel. The Older Way If you dig through much driver code in the 2.6 kernel, you may notice that quite a few char drivers do not use the cdev interface that we have just described. What you are seeing is older code that has not yet been upgraded to the 2.6 interface.
Since that code works as it is, this upgrade may not happen for a long time. For completeness, we describe the older char device registration interface, but new code should not use it; this mechanism will likely go away in a future kernel. The classic way to register a char device driver is with: int registerchrdev(unsigned int major, const char.name, struct fileoperations.fops); Here, major is the major number of interest, name is the name of the driver (it appears in /proc/devices), and fops is the default fileoperations structure. A call to registerchrdev registers minor numbers 0-255 for the given major, and sets up a default cdev structure for each.
Drivers using this interface must be prepared to handle open calls on all 256 minor numbers (whether they correspond to real devices or not), and they cannot use major or minor numbers greater than 255. If you use registerchrdev, the proper function to remove your device(s) from the system is: int unregisterchrdev(unsigned int major, const char.name); major and name must be the same as those passed to registerchrdev, or the call will fail. Scull's Memory Usage Before introducing the read and write operations, we'd better look at how and why scull performs memory allocation.
'How' is needed to thoroughly understand the code, and 'why' demonstrates the kind of choices a driver writer needs to make, although scull is definitely not typical as a device. This section deals only with the memory allocation policy in scull and doesn't show the hardware management skills you need to write real drivers.
These skills are introduced in. Therefore, you can skip this section if you're not interested in understanding the inner workings of the memory-oriented scull driver. The region of memory used by scull, also called a device, is variable in length. The more you write, the more it grows; trimming is performed by overwriting the device with a shorter file. The scull driver introduces two core functions used to manage memory in the Linux kernel.
These functions, defined in, are: void.kmalloc(sizet size, int flags); void kfree(void.ptr); A call to kmalloc attempts to allocate size bytes of memory; the return value is a pointer to that memory or NULL if the allocation fails. The flags argument is used to describe how the memory should be allocated; we examine those flags in detail in.
For now, we always use GFPKERNEL. Allocated memory should be freed with kfree. You should never pass anything to kfree that was not obtained from kmalloc. It is, however, legal to pass a NULL pointer to kfree. Kmalloc is not the most efficient way to allocate large areas of memory (see ), so the implementation chosen for scull is not a particularly smart one.
The source code for a smart implementation would be more difficult to read, and the aim of this section is to show read and write, not memory management. That's why the code just uses kmalloc and kfree without resorting to allocation of whole pages, although that approach would be more efficient. On the flip side, we didn't want to limit the size of the 'device' area, for both a philosophical reason and a practical one. Philosophically, it's always a bad idea to put arbitrary limits on data items being managed.
Practically, scull can be used to temporarily eat up your system's memory in order to run tests under low-memory conditions. Running such tests might help you understand the system's internals. You can use the command cp /dev/zero /dev/scull0 to eat all the real RAM with scull, and you can use the dd utility to choose how much data is copied to the scull device. In scull, each device is a linked list of pointers, each of which points to a sculldev structure. Each such structure can refer, by default, to at most four million bytes, through an array of intermediate pointers. The released source uses an array of 1000 pointers to areas of 4000 bytes.
We call each memory area a quantum and the array (or its length) a quantum set. A scull device and its memory areas are shown in. Read and write The read and write methods both perform a similar task, that is, copying data from and to application code. Therefore, their prototypes are pretty similar, and it's worth introducing them at the same time: ssizet read(struct file.filp, char user.buff, sizet count, lofft.offp); ssizet write(struct file.filp, const char user.buff, sizet count, lofft.offp); For both methods, filp is the file pointer and count is the size of the requested data transfer. The buff argument points to the user buffer holding the data to be written or the empty buffer where the newly read data should be placed.
Finally, offp is a pointer to a 'long offset type' object that indicates the file position the user is accessing. The return value is a 'signed size type'; its use is discussed later. Let us repeat that the buff argument to the read and write methods is a user-space pointer. Therefore, it cannot be directly dereferenced by kernel code. There are a few reasons for this restriction.
Depending on which architecture your driver is running on, and how the kernel was configured, the user-space pointer may not be valid while running in kernel mode at all. There may be no mapping for that address, or it could point to some other, random data. Even if the pointer does mean the same thing in kernel space, user-space memory is paged, and the memory in question might not be resident in RAM when the system call is made. Attempting to reference the user-space memory directly could generate a page fault, which is something that kernel code is not allowed to do. The result would be an 'oops,' which would result in the death of the process that made the system call. The pointer in question has been supplied by a user program, which could be buggy or malicious. If your driver ever blindly dereferences a user-supplied pointer, it provides an open doorway allowing a user-space program to access or overwrite memory anywhere in the system.
If you do not wish to be responsible for compromising the security of your users' systems, you cannot ever dereference a user-space pointer directly. Obviously, your driver must be able to access the user-space buffer in order to get its job done. This access must always be performed by special, kernel-supplied functions, however, in order to be safe. We introduce some of those functions (which are defined in ) here, and the rest in the; they use some special, architecture-dependent magic to ensure that data transfers between kernel and user space happen in a safe and correct way.
The code for read and write in scull needs to copy a whole segment of data to or from the user address space. This capability is offered by the following kernel functions, which copy an arbitrary array of bytes and sit at the heart of most read and write implementations: unsigned long copytouser(void user.to, const void.from, unsigned long count); unsigned long copyfromuser(void.to, const void user.from, unsigned long count); Although these functions behave like normal memcpy functions, a little extra care must be used when accessing user space from kernel code. The user pages being addressed might not be currently present in memory, and the virtual memory subsystem can put the process to sleep while the page is being transferred into place. This happens, for example, when the page must be retrieved from swap space.
The net result for the driver writer is that any function that accesses user space must be reentrant, must be able to execute concurrently with other driver functions, and, in particular, must be in a position where it can legally sleep. We return to this subject in. The role of the two functions is not limited to copying data to and from user-space: they also check whether the user space pointer is valid. If the pointer is invalid, no copy is performed; if an invalid address is encountered during the copy, on the other hand, only part of the data is copied. In both cases, the return value is the amount of memory still to be copied. The scull code looks for this error return, and returns -EFAULT to the user if it's not 0.
The topic of user-space access and invalid user space pointers is somewhat advanced and is discussed in. However, it's worth noting that if you don't need to check the user-space pointer you can invoke copytouser and copyfromuser instead. This is useful, for example, if you know you already checked the argument. Be careful, however; if, in fact, you do not check a user-space pointer that you pass to these functions, then you can create kernel crashes and/or security holes. As far as the actual device methods are concerned, the task of the read method is to copy data from the device to user space (using copytouser), while the write method must copy data from user space to the device (using copyfromuser). Each read or write system call requests transfer of a specific number of bytes, but the driver is free to transfer less data—the exact rules are slightly different for reading and writing and are described later in this chapter. Whatever the amount of data the methods transfer, they should generally update the file position at.offp to represent the current file position after successful completion of the system call.
The kernel then propagates the file position change back into the file structure when appropriate. The pread and pwrite system calls have different semantics, however; they operate from a given file offset and do not change the file position as seen by any other system calls. These calls pass in a pointer to the user-supplied position, and discard the changes that your driver makes. Represents how a typical read implementation uses its arguments. Figure 3-2. The arguments to read Both the read and write methods return a negative value if an error occurs. A return value greater than or equal to 0, instead, tells the calling program how many bytes have been successfully transferred. If some data is transferred correctly and then an error happens, the return value must be the count of bytes successfully transferred, and the error does not get reported until the next time the function is called.
Implementing this convention requires, of course, that your driver remember that the error has occurred so that it can return the error status in the future. Although kernel functions return a negative number to signal an error, and the value of the number indicates the kind of error that occurred (as introduced in ), programs that run in user space always see -1 as the error return value. They need to access the errno variable to find out what happened. The user-space behavior is dictated by the POSIX standard, but that standard does not make requirements on how the kernel operates internally. Playing with the New Devices Once you are equipped with the four methods just described, the driver can be compiled and tested; it retains any data you write to it until you overwrite it with new data.
The device acts like a data buffer whose length is limited only by the amount of real RAM available. You can try using cp, dd, and input/output redirection to test out the driver.
The free command can be used to see how the amount of free memory shrinks and expands according to how much data is written into scull. To get more confident with reading and writing one quantum at a time, you can add a printk at an appropriate point in the driver and watch what happens while an application reads or writes large chunks of data. Alternatively, use the strace utility to monitor the system calls issued by a program, together with their return values. Tracing a cp or an ls -l /dev/scull0 shows quantized reads and writes.
Monitoring (and debugging) techniques are presented in detail in. #include devt devt is the type used to r epresent device numbers within the kernel. Int MAJOR(devt dev); int MINOR(devt dev); Macros that extract the major and minor numbers from a device number. Devt MKDEV(unsigned int major, unsigned int minor); Macro that builds a devt data item from the major and minor numbers.
#include The 'filesystem' header is the header required for writing device drivers. Many important functions and data structures are declared in here. Int registerchrdevregion(devt first, unsigned int count, char.name) int allocchrdevregion(devt.dev, unsigned int firstminor, unsigned int count, char.name) void unregisterchrdevregion(devt first, unsigned int count); Functions that allow a driver to allocate and free ranges of device numbers.
Registerchrdevregion should be used when the desired major number is known in advance; for dynamic allocation, use allocchrdevregion instead. Int registerchrdev(unsigned int major, const char.name, struct fileoperations.fops); The old (pre-2.6) char device registration routine. It is emulated in the 2.6 kernel but should not be used for new code. If the major number is not 0, it is used unchanged; otherwise a dynamic number is assigned for this device. Int unregisterchrdev(unsigned int major, const char.name); Function that undoes a registration made with registerchrdev.
Both major and the name string must contain the same values that were used to register the driver. Struct fileoperations; struct file; struct inode; Three important data structures used by most device drivers. The fileoperations structure holds a char driver's methods; struct file represents an open file, and struct inode represents a file on disk.
#include struct cdev.cdevalloc(void); void cdevinit(struct cdev.dev, struct fileoperations.fops); int cdevadd(struct cdev.dev, devt num, unsigned int count); void cdevdel(struct cdev.dev); Functions for the management of cdev structures, which represent char devices within the kernel. #include containerof(pointer, type, field); A convenience macro that may be used to obtain a pointer to a structure from a pointer to some other structure contained within it. #include This include file declares functions used by kernel code to move data to and from user space.
Unsigned long copyfromuser (void.to, const void.from, unsigned long count); unsigned long copytouser (void.to, const void.from, unsigned long count); Copy data between user space and kernel space.