One 、linux The system will install the device It is divided into 3 class : Character device 、 Block device 、 Network devices . Using drivers :

1、 Character device : A device that can only read and write one byte at a time , Can't read a data in device memory randomly , Reading data needs to follow the order of data . Character devices are stream oriented devices , Common character devices are mouse 、 keyboard 、 A serial port 、 Console and LED Equipment etc. .
2、 Block device : It refers to a device that can read a certain length of data from any position of the device . Block devices include hard disks 、 disk 、U Plate and SD Card, etc .

Every character device or block device is in /dev The directory corresponds to a device file .linux User program through device file ( Or device node ) To use drivers to operate character devices and block devices .


Two 、 Character device driver Foundation :
1、 Main equipment number and secondary equipment number ( Together, they are the equipment number ):
A character device or block device has a primary device number and a secondary device number . The master device number is used to identify the driver connected to the device file , Used to reflect the type of equipment . The secondary device number is used by the driver to identify which device is operating , Used to distinguish the same type of equipment .
linux The kernel , For equipment No dev_t To describe ,2.6.28 Is defined as follows :
typedef u_long dev_t;
stay 32 In the bit machine is 4 Bytes , high 12 Bits indicate the master device number , low 12 Bits indicate the secondary device number .

You can use the following macros from dev_t Get the primary and secondary device number in : You can also use the following macro to generate from primary and secondary device numbers dev_t:
MAJOR(dev_t dev);  MKDEV(int major,int minor);
MINOR(dev_t dev);


// Macro definition :
#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)
#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))


2、 Assign device number ( The two methods ):

(1) Static application :
int register_chrdev_region(dev_t from, unsigned count, const char *name);


 * register_chrdev_region() - register a range of device numbers
 * @from: the first in the desired range of device numbers; must include
 *        the major number.
 * @count: the number of consecutive device numbers required
 * @name: the name of the device or driver.
 * Return value is zero on success, a negative error code on failure.

(2) Dynamic allocation :

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);


int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
 * alloc_chrdev_region() - register a range of char device numbers
 * @dev: output parameter for first assigned number
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: the name of the associated device or driver
 * Allocates a range of char device numbers.  The major number will be
 * chosen dynamically, and returned (along with the first minor number)
 * in @dev.  Returns zero or a negative error code.

Cancel device number

void unregister_chrdev_region(dev_t from, unsigned count);

Create device files
utilize cat /proc/devices Check the device name you have applied for , Device number .
(1) Use mknod Create... By hand :mknod filename type major minor
(2) Automatically create ;

utilize udev(mdev) To realize the automatic creation of device files , First of all, support should be guaranteed udev(mdev), from busybox To configure . Call... In the driver initialization code class_create Create a class, Call... For each device device_create Create the corresponding device .


3、 Character device driver is an important data structure
(1)struct file: Represents an open file descriptor , Every open file in the system has an associated... In the kernel struct file. It's built by the kernel in open Created on , And pass it to any function that operates on the file , Until it's finally closed . When all instances of the file are closed , The kernel releases this data structure .

// Key members :     
const struct file_operations    *f_op;  // This operation defines the operation associated with the file . The kernel is executing open Assign a value to this pointer . 
off_t  f_pos;     // The file read and write location .
void            *private_data;// This member is a very useful resource to save state information during system call .

(2)struct inode: It's used to record the physical information of a file . It's the same as the open file The structure is different . One file can correspond to multiple file structure , But there's only one inode structure .inode As a general file_operations The parameters of the function in the structure are passed .
inode Translated into Chinese is the index node . Each storage device or partition of a storage device ( The storage device is a hard disk 、 floppy disk 、U disc ... ... ) After being formatted as a file system , There should be two parts , Part of it is inode, The other part is Block,Block It's used to store data . and inode Well , It's the information that's used to store this data , This information includes file size 、 Belong to 、 The user group to which it belongs 、 Read and write permissions, etc .inode Index information for each file , So there it is inode The numerical . The operating system follows instructions , Can pass inode Value of the fastest to find the corresponding file .

dev_t i_rdev;    // To represent a device file inode structure , This field contains the real device number .
struct cdev *i_cdev;     // It represents the internal structure of the kernel of the character device . When inode When pointing to a character device file , This field contains a pointer to struct cdev Pointer to structure .
// We can also use the following two macros from inode Get the master device number and this device number from :
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

(3)struct file_operations


struct file_operations ***_ops={
 .owner =  THIS_MODULE,
 .llseek =  ***_llseek,
 .read =  ***_read,
 .write =  ***_write,
 .ioctl =  ***_ioctl,
 .open =  ***_open,
 .release = ***_release, 
 ...  ...
struct module *owner;
 /* first  file_operations  Member is not an operation at all ;  It's a pointer to the module that owns the structure .
  This member is used to prevent the module from being unloaded while its operation is still in use .  Almost all the time ,  It is simply initialized to  
THIS_MODULE,  In a  <linux/module.h>  Macro defined in . This macro is more complex , When doing simple learning operations , It is usually initialized to THIS_MODULE.*/
loff_t (*llseek) (struct file * filp , loff_t  p,  int  orig);
/*( Pointer parameter filp Pointer to the target file structure for reading information ; Parameters  p  The target offset for the file location ; Parameters orig To locate files
From , This value can be at the beginning of the file (SEEK_SET,0, The current position (SEEK_CUR,1), end of file (SEEK_END,2))
llseek  Method is used to change the current read in the file / Write location ,  And the new location serves as ( Positive ) Return value .
loff_t  The parameter is one "long offset",  And even in  32 And at least  64  A wide .  The error is indicated by a negative return value .
If the function pointer is  NULL, seek  The call can be modified in potentially unpredictable ways  file  The position counter in the structure (  stay "file  structure "  In this section ).*/
ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);
/*( Pointer parameter  filp  The target file for reading information , Pointer parameter buffer  The buffer that places information for the corresponding ( The user space memory address ),
Parameters size For the length of information to read , Parameters  p  The offset of the read position from the beginning of the file , After reading the information , This pointer usually moves , The moved value is the length value of the information to be read )
This function is used to get data from the device .  A null pointer at this position causes  read  System call to  -EINVAL("Invalid argument")  Failure .
  A nonnegative return value represents the number of bytes successfully read (  The return value is one  "signed size"  type ,  It's often an integer type local to the target platform ).*/
ssize_t (*aio_read)(struct kiocb *  , char __user *  buffer, size_t  size ,  loff_t   p);
/* It can be seen that , The first part of this function is 、 Three parameters and in this structure read() The first part of the function 、 The three parameters are different   Of ,
The third parameter of asynchronous read-write directly passes the value , The third parameter of synchronous read-write is the pointer , because AIO Never need to change the location of the file .
The first parameter of asynchronous read / write is pointing to kiocb Pointer to structure , The first parameter of synchronous read-write is pointing to file Pointer to structure , every last I/O All requests correspond to one kiocb Structure );
Initialize an asynchronous read  --  Read operations that may not end before the function returns . If this method is  NULL,  All operations will be performed by  read  Instead of doing ( Synchronously ).
( of linux asynchronous I/O, You can refer to the relevant information ,《linux Detailed description of device driven development 》 Detailed solutions are given in )*/
ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);
/*( Parameters filp The structure body is the pointer to the target file ,buffer For the information buffer to write to the file ,count For the length of the information to be written ,
ppos Is the current offset position , This value is usually used to determine whether the write file is out of bounds )
Send data to the device .  If  NULL, -EINVAL  Return to call  write  The program called by the system .  If it's not negative ,  The return value represents the number of bytes successfully written .
( notes : This operation and the above operation of reading a file are both blocking operations )*/
ssize_t (*aio_write)(struct kiocb *, const char __user *  buffer, size_t  count, loff_t * ppos);
/* Initialize an asynchronous write on the device . The parameter type is the same as aio_read() function ;*/
int (*readdir) (struct file *  filp, void *, filldir_t);
/* For device files, this member should be  NULL;  It's used to read directories ,  And only useful for file systems .*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/*( This is a polling function in the device driver , The first parameter is zero file Structure pointer , The second is the polling table pointer )
This function returns the available state of the device resource , namely POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL The bit of an isomacro “ or ” result .
Each macro indicates a state of the device , Such as :POLLIN( Defined as 0x0001) It means that the device can read without blocking ,POLLOUT( Defined as 0x0004) It means that the device can write without blocking .
(poll  The method is  3  The back end of a system call : poll, epoll,  and  select,  Both are used to query whether reading or writing to one or more file descriptors will block .
 poll  Method should return a bitmask indicating whether non blocking read or write is possible ,  also ,  Probably ,  The information provided to the kernel is used to make the calling process sleep until  I/O  possible . 
If a driver  poll  Method is  NULL,  The device is assumed to be non blocking, readable and writable .
( Here, the device is usually regarded as a file for related operations , The value of polling operation is directly related to the response of the device , It can be the result of a blocking operation , It can also be the result of a non blocking operation )*/
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
/*(inode  and  filp  The pointer is the file descriptor passed by the corresponding application  fd  Value ,  And pass it on to  open  The same parameters of the method .
cmd  Parameters are passed down from the user unchanged ,  And optional parameters  arg  Parameter with a  unsigned long  Form transfer ,  Whether it is given by the user as an integer or a pointer .
If the calling program does not pass the second  3  Parameters ,  Received by the driven operation  arg  Values are undefined .
Because type checking is turned off on this extra parameter ,  The compiler cannot warn you if an invalid parameter is passed to  ioctl,  And any associated errors will be hard to find .)
ioctl  System calls provide a way to issue device specific commands ( For example, formatting a track on a floppy disk ,  It's not reading or writing ).  in addition ,  How many?  ioctl  Commands are recognized by the kernel and do not have to be referenced  fops  surface .
  If the device is not available  ioctl  Method ,  For any undefined request (-ENOTTY, " The device has no such  ioctl"),  The system call returned an error .*/
int (*mmap) (struct file *, struct vm_area_struct *);
/*mmap  Used to request that device memory be mapped to the address space of the process .  If this method is  NULL, mmap  System call return  -ENODEV.
( If you want to have a thorough understanding of this function , So please see about “ Process address space ” Introduction books )*/
int (*open) (struct inode * inode , struct file *  filp ) ;
/*(inode  For file nodes , There is only one node , No matter how many files a user opens , They all correspond to one inode structure ;
however filp It's different , Just open a file , It corresponds to a file Structure ,file Structs are usually used to track the state information of a file at run time )
  Although this is often the first operation on the device file ,  The driver is not required to declare a corresponding method .  If this item is  NULL,  The device has been turned on successfully ,  But your driver won't be notified .
And open() The function corresponds to release() function .*/
int (*flush) (struct file *);
/*flush  Operation is called when a process closes a copy of its device file descriptor ;  It should carry out ( And wait for ) Any unfinished operation of the device .
This must not query the request with the user  fsync  The operation is confused .  At present , flush  Used in very few drives ;
 SCSI  The tape driver uses it ,  for example ,  To ensure that all written data is written to tape before the device is shut down .  If  flush  by  NULL,  The kernel simply ignores requests from user applications .*/
int (*release) (struct inode *, struct file *);
/*release () Function is executed when the last user process to open the device close() When the system calls , The kernel will call the driver release() function :
void release(struct inode inode,struct file *file),release The main task of the function is to clean up the unfinished input and output operations , Release resources , Reset of user-defined exclusive flag, etc .
     Reference this operation when the file structure is released .  Like  open, release  It can be for  NULL.*/
int(*synch)(struct file *,struct dentry *,int datasync);
// Refresh the pending data , Allow the process to flush all dirty buffers to disk .
int (*aio_fsync)(struct kiocb *, int);
 /* This is a  fsync  Asynchronous version of method . So-called fsync Method is a system call function . system call fsync
Write all the dirty buffers of the file specified by the file to disk ( if necessary , It also includes the buffer that holds the inode ).
The corresponding service routine gets the address of the file object , And then call fsync Method . Usually this method calls the function __writeback_single_inode() end ,
This function writes the dirty pages associated with the selected inode and the inode itself back to disk .*/
int (*fasync) (int, struct file *, int);
// The driver of this function is the notification system , Here's the template for this function :
static int ***_fasync(int fd,struct file *filp,int mode)
    struct ***_dev * dev=filp->private_data;
    return fasync_helper(fd,filp,mode,&dev->async_queue);// The fourth parameter is  fasync_struct The pointer structure of the body .
// This function is used to deal with FASYNC Flag function .(FASYNC: Indicates compatibility BSD Of fcntl Synchronous operation ) When the sign changes , In the driver fasync() The function will be executed .
/* This operation is used to inform the device of its  FASYNC  The change of logo .  Asynchronous notification is a high-level topic ,  In the  6  The chapter describes .
This member can be NULL  If the driver does not support asynchronous notification .*/
int (*lock) (struct file *, int, struct file_lock *);
//lock  Method is used to lock the file ;  Locking is an essential feature of regular files ,  But device drivers almost never implement it .
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
/* These methods achieve divergence / Aggregate read and write operations .  Applications occasionally need to do a single read or write operation that contains multiple memory areas ;
  These system calls allow them to do so without having to make additional copies of the data .  If these function pointers are  NULL, read  and  write  Method is called (  Maybe more than once  ).*/
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
/* This method implements  sendfile  System call read ,  Move data from one file descriptor to another with minimal copies .
for example ,  A content that needs to be sent to the network  web  Server usage .  Device drivers often make  sendfile  by  NULL.*/
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
/*sendpage  yes  sendfile  The other half of ;  It is called by the kernel to send data ,  One page at a time ,  To the corresponding file .  Device drivers don't actually implement  sendpage.*/
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
/* The purpose of this method is to find a suitable location in the address space of the process to map in the memory segment of the underlying device .
This task is usually performed by memory management code ;  This method exists to enable the driver to force any alignment request that a particular device may have .  Most drivers can set this method to  NULL.[10]*/
int (*check_flags)(int)
// This method allows module checking to be passed to  fnctl(F_SETFL...)  The flag of the call .
int (*dir_notify)(struct file *, unsigned long);
// This method is used in applications  fcntl  To request directory change notification .  Useful only for file systems ;  Drivers don't need to be implemented  dir_notify.


3、 ... and 、 Character device driver design

1. Device registration
stay linux2.6 The kernel , Character devices use struct cdev To describe ;


struct cdev
  struct kobject kobj;// embedded kobject object
  struct module *owner;// The module it belongs to
  struct file_operations *ops;// File operation structure
  struct list_head list;
  dev_t dev;// Device number , The length is 32 position , Among them high 12 Main equipment number , low 20 This is the device number
  unsigned int count;


The registration of character devices is divided into three steps :

(1) Distribute cdev: struct cdev *cdev_alloc(void);
(2) initialization cdev: void cdev_init(struct cdev *cdev, const struct file_operations *fops);
(3) add to cdev: int cdev_add(struct cdev *p, dev_t dev, unsigned count)


 * cdev_add() - add a char device to the system
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this
 *         device
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.


2. Implementation of device operation :file_operations Implementation of function set ( To know when a function is called ? Call to do something ?)
Particular attention : Driver application data exchange :
Data exchange between driver and application is very important .file_operations Medium read() and write() function , It's used to exchange data between drivers and applications . Through data exchange , Drivers and applications can learn about each other . But drivers and applications belong to different address spaces . The driver cannot directly access the address space of the application ; Similarly, applications cannot directly access the address space of the driver , Otherwise, it will destroy the data in each other's space , And then the system crashes , Or data corruption . The safe way is to use the special functions provided by the kernel , Complete data exchange in application space and driver space . These functions strictly check and convert the pointer passed by the user program , So as to ensure the security of data exchange between user program and driver . These functions are :

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n); 
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n); 


3. Device logout :void cdev_del(struct cdev *p);

Four 、 Summary of character device driver :

The character device is 3 Big equipment ( Character device 、 Block device 、 Network devices ) A simpler type of equipment in the , The main work in the driver is initialization 、 Add and remove cdev Structure , Apply and release device number , And filling file_operation Operation function in structure , And implement file_operations In structure read()、write()、ioctl() And so on . As shown in the picture cdev Structure 、file_operations And user space call driver .


5、 ... and : Character device driver analysis :


View Code




static mem_major = MEMDEV_MAJOR;
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /* Device structure pointer */
struct cdev cdev; 
/* File open function */
int mem_open(struct inode *inode, struct file *filp)
    struct mem_dev *dev;
    /* Get the secondary device number */
    int num = MINOR(inode->i_rdev);
    if (num >= MEMDEV_NR_DEVS) 
            return -ENODEV;
    dev = &mem_devp[num];
    /* Assign device description structure pointer to file private data pointer */
    filp->private_data = dev;
    return 0; 
/* File release function */
int mem_release(struct inode *inode, struct file *filp)
  return 0;
/* Read function */
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
  unsigned long p =  *ppos;        /* Record file pointer offset position */  
  unsigned int count = size;    /* Record the number of bytes to read */ 
  int ret = 0;    /* Return value */  
  struct mem_dev *dev = filp->private_data; /* Get the device structure pointer */
  /* Judge whether the read position is valid */
  if (p >= MEMDEV_SIZE)    /* The offset to read is larger than the memory space of the device */  
    return 0;
  if (count > MEMDEV_SIZE - p)     /* The bytes to read are larger than the memory space of the device */ 
    count = MEMDEV_SIZE - p;
  /* Read data into user space : Kernel space -> User space exchange data */  
  if (copy_to_user(buf, (void*)(dev->data + p), count))
    ret =  - EFAULT;
    *ppos += count;
    ret = count;
    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  return ret;
/* Write function */
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  struct mem_dev *dev = filp->private_data; /* Get the device structure pointer */
  /* Analyze and get effective write length */
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)    /* The bytes to be written are larger than the memory space of the device */
    count = MEMDEV_SIZE - p;
  /* Write data from user space */
  if (copy_from_user(dev->data + p, buf, count))
    ret =  - EFAULT;
    *ppos += count;      /* Increase the offset position */  
    ret = count;      /* Returns the actual number of bytes written */ 
    printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
  return ret;
/* seek File location function  */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
    loff_t newpos;      
    switch(whence) {
      case 0: /* SEEK_SET */       /* Offset from the beginning of the file */ 
        newpos = offset;           /* Update file pointer location */
      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;    
      case 2: /* SEEK_END */
        newpos = MEMDEV_SIZE -1 + offset;
      default: /* can't happen */
        return -EINVAL;
    if ((newpos<0) || (newpos>MEMDEV_SIZE))
        return -EINVAL;
    filp->f_pos = newpos;
    return newpos;
/* File operation structure */
static const struct file_operations mem_fops =
  .owner = THIS_MODULE,
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
/* Device driver module loading function */
static int memdev_init(void)
  int result;
  int i;
  dev_t devno = MKDEV(mem_major, 0);
   /*  Apply for equipment number , When xxx_major Not for 0 when , Represents a static assignment ; When it comes to 0 when , It means dynamic application */ 
  /*  Static application equipment number */
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /*  Dynamically assign device numbers  */
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);    /* Get the master device number of the application */
  if (result < 0)
    return result;
 /* initialization cdev structure , And transmission file_operations Structure pointer */ 
  cdev_init(&cdev, &mem_fops);    
  cdev.owner = THIS_MODULE;    /* Specify the module to which it belongs */
  cdev.ops = &mem_fops;
  /*  Register character device  */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
  /*  Allocate memory for the device description structure */
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  if (!mem_devp)    /* Application failed */
    result =  - ENOMEM;
    goto fail_malloc;
  memset(mem_devp, 0, sizeof(struct mem_dev));
  /* Allocate memory for devices */
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  return 0;
  unregister_chrdev_region(devno, 1);
  return result;
/* Module unload function */
static void memdev_exit(void)
  cdev_del(&cdev);   /* Log off the device */
  kfree(mem_devp);     /* Free device structure memory */
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /* Release the device number */



(3) Applications ( The test file ):app-mem.c


#include <stdio.h>
int main()
    FILE *fp0 = NULL;
    char Buf[4096];
    /* initialization Buf*/
    strcpy(Buf,"Mem is char dev!");
    printf("BUF: %s\n",Buf);
    /* Open device file */
    fp0 = fopen("/dev/memdev0","r+");
    if (fp0 == NULL)
        printf("Open Memdev0 Error!\n");
        return -1;
    /* Write device */
    fwrite(Buf, sizeof(Buf), 1, fp0);
    /* Relocate the file ( Thinking doesn't have that directive , What will happen )*/
    /* eliminate Buf*/
    strcpy(Buf,"Buf is NULL!");
    printf("BUF: %s\n",Buf);
    /* Readout devices */
    fread(Buf, sizeof(Buf), 1, fp0);
    /* detection result */
    printf("BUF: %s\n",Buf);
    return 0;    


testing procedure :

1)cat /proc/devices See which numbers have been used , Let's choose one that's not in use XXX.

2)insmod memdev.ko

3) adopt "mknod /dev/memdev0 c XXX 0" Command to create "/dev/memdev0" Device node .

4) Cross compilation app-mem.c file , Download and execute :

#./app-mem, Show :

Mem is char dev!