CONTENTS : Kernel frameworks for device drivers Block vs. character devices Interaction of user space applications with the kernel Details on character devices, file_operations , ioctl (), etc. Exchanging data to/from user space device managed allocations
Kerne l framework The diagram provides a detailed view of the Linux kernel , illustrating the relationship between different kernel subsystems, software support features, and hardware components.
Overall architecture:
Block vs. character devices Aspect Block Devices Character Devices Data Handling Handle data in blocks or chunks (e.g., 512 bytes). Handle data as a stream of characters. Access Method Allows random access to data. Provides sequential access to data. Examples Hard drives, SSDs, USB drives. Serial ports, keyboards, mice. File System Usually has a file system that organizes data in blocks. Does not use a file system. Buffering Typically uses buffers to manage block transfers. Data is read or written one character at a time. Usage Used for devices where data is stored and retrieved in blocks. Used for devices where data is processed as a continuous stream. I/O Operations Blocked I/O operations (e.g., read/write operations with offsets). Unblocked I/O operations (e.g., read/write operations directly). Performance Generally optimized for high-throughput operations with large data sizes. Optimized for low-latency operations with small data sizes.
Interaction of user space applications with the kernel System Calls. Device Drivers. Memory Management . Signals. IPC (Inter-Process Communication). File Systems . Networking. IOCTL .
Details on character devices, file_operations , ioctl () : Character devices : Character devices are a fundamental part of Unix-like operating systems and provide a way to interface with hardware or software components that handle data as a stream of characters. Definition : Character devices handle data as a continuous stream of bytes or characters. They do not support random access; data is processed sequentially. Examples : Serial ports, keyboards, mouse, printers, and other devices where data is read or written one character at a time. Fig. Examples of character devices
Major Components of Character Devices Device File : Character devices are represented by device files in the /dev directory (e.g., /dev/ ttyS0 for a serial port). These files provide an interface through which user space applications can interact with the device. Device Driver : The device driver implements the operations that can be performed on the character device. It manages interactions between the hardware or software component and the operating system.
Key Functions in Character Device Drivers file_operations Structure : The file_operations structure defines the operations that can be performed on a character device. Common fields in this structure include: open (): Called when a file descriptor is opened for the device. release (): Called when a file descriptor is closed. read (): Called to read data from the device. write (): Called to write data to the device. ioctl (): Used for device-specific control operations. llseek (): Used for seeking to a specific position in the file (typically not used in character devices).
Example of file_operations structure:
Registering a Character Device : The character device must be registered with the kernel, usually done during module initialization. This involves calling functions like register_chrdev () or using the newer cdev API. Example of registering a character device: int major; struct cdev my_cdev ; dev_t dev; alloc_chrdev_region (&dev, 0, 1, " my_device "); major = MAJOR(dev); cdev_init (& my_cdev , & my_fops ); cdev_add (& my_cdev , dev, 1);
Unregistering a Character Device : When the device is no longer needed, it should be unregistered. Example of unregistering: cdev_del (& my_cdev ); unregister_chrdev_region (dev, 1);
ioctl() System Call: Purpose : Provides a way to perform device-specific control operations that are not covered by standard system calls. Usage : The ioctl () function allows user space applications to pass commands to the device driver. These commands can control hardware features or query device status. Syntax : int ioctl (int fd , unsigned long request, ...); whe re, fd : File descriptor of the device. request : Command to be performed (typically defined by device-specific headers). ... : Additional arguments depending on the command.
request : That is, an ioctl number is constructed from 4 fields, from upper to lower: dir - direction, 2 bits. Upper bit denotes that a user writes an argument, Lower bit denotes that a user reads an argument. size - size of the argument, 14 bits. type/magic - a number uniquely representing a driver, 8 bit. number - a number which is unique for the type (for the driver), 8 bit. Example of ioctl cmd : (refer. Documentation/ioctl-number.txt ) take example 0xc0104102 Binary – 11 00 0000 0001 0000 0100 0001 0000 0010 dir - 0x3 (both read and write). - Bits[31:30] size - 0x10 (size of the structure, 16). - Bits[29:16] type - 0x41 (ASCII code of the character A). - Bits[15:8] nr - 0x2 (the second argument). - Bits[7:0]
Exchanging data to/from user space
Device memory buffer User buffer (memory of the process address space ) Kernel space (non swappable) User space ( s w ap p able) read Co p y_ t o_ u s e r ()
Destination ad d r e s s, in user space. Source address, in kernel space Number of bytes to copy Returns on success or number of bytes that could not be copied If this function returns non zero value , you should assume that there was a problem during data copy.So return appropriate error code (-EFAULT)
Device memory buffer User buffer (memory of the process address space ) Kernel space (non swappable) User space ( s w ap p able) write Co p y_ f r om_ u s er ()
get_user () /** * get_user : - Get a simple variable from user space. * @x: Variable to store result. @ptr: Source address, in user space. #define get_user (x, ptr ) \ __ get_user_check ((x), ( ptr ), sizeof (*( ptr ))) This macro copies a single simple variable from user space to kernel * space. It supports simple types like char and int, but not larger * data types like structures or arrays. Returns zero on success, or -EFAULT on error. * On error, the variable @x is set to zero.
put_user () * put_user : - Write a simple value into user space. * @x: Value to copy to user space. * @ptr: Destination address, in user space. #define put_user (x, ptr ) \ __ put_user_check ((__ typeof __(*( ptr )))(x), ( ptr ), sizeof (*( ptr ))) This macro copies a single simple value from kernel space to user * space. It supports simple types like char and int, but not larger * data types like structures or arrays. Returns zero on success, or -EFAULT on error.
device managed allocations : kmalloc () kfree () Include include/ linux / slab.h to use allocations.
/** kmalloc - allocate memory void * kmalloc ( size_t size, gfp_t flags); @size: how many bytes of memory are required. @flags: the type of memory to allocate. -> GFP_KERNEL : Allocate normal kernel ram. May sleep. -> GFP_NOWAIT : Allocation will not sleep. -> GFP_ATOMIC Allocation will not sleep. May use emergency pools. -> GFP_HIGHUSER Allocate memory from high memory on behalf of user. return = NULL if allocation fails, on success virtual address of the first page allocated
kfree () : /** kfree - free previously allocated memory @objp: pointer returned by kmalloc . * If @objp is NULL, no operation is performed. * Don't free memory not originally allocated by kmalloc () or you will run into trouble. * void kfree ( const void * objp );
Other Kernel functions: kzalloc () krealloc () kzfree ()
Kzalloc is preferred over kmalloc /** kzalloc - allocate memory. The memory is set to zero. @size: how many bytes of memory are required. @flags: the type of memory to allocate (see kmalloc ). */ void * kzalloc ( size_t size, gfp_t flags);
struct bar { int a ; int b; char *name; } struct bar *pbar; pbar = kmalloc( sizeof(*pbar) , GFP_KERNEL); bar_processing_fun(pbar); void bar_processing_fun(struct bar *pbar) { if(! (pbar->name) ) //allocate memory for ‘name’ else //memory for ‘name’ is already allocated /* This may crash if pbar->name is not a valid pointer */ memcpy(pbar->name, “name”,5); }
krealloc () /** krealloc - reallocate memory. The contents will remain unchanged. @p: object to reallocate memory for. @new_size: how many bytes of memory are required. @flags: the type of memory to allocate. * The contents of the object pointed to are preserved up to the lesser of the new and old sizes. If @p is %NULL, krealloc () behaves exactly like kmalloc (). If @new_size is and @p is not a %NULL pointer, the object pointed to is freed. * Return: pointer to the allocated memory or %NULL in case of error */ void * krealloc ( const void *p, size_t new_size , gfp_t flags);
kzfree () /** kzfree - like kfree but zero memory @p: object to free memory of * The memory of the object @p points to is zeroed before freed. If @p is %NULL, kzfree () does nothing. * Note: this function zeroes the whole allocated buffer which can be a good deal bigger than the requested buffer size passed to kmalloc (). So be careful when using this function in performance sensitive code. */ void kzfree ( const void *p);
Resource managed kernel APIs kmalloc () //This allocates a resource ( kernel memory) devm_kmalloc () //This also allocates a resource but it “remembers” what has been allocated. (This is resource managed API)
kmalloc (); Programmer must free the memory using kfree (); devm_kmalloc (); Programmers using kfree () is not required. Kernel will take care of freeing memory when the “device” or managing “driver” gets removed from the system Struct device (platform device) Memory is allocated on behalf of this device