关于字符设备驱动的通用概念和写法

概述

  • 设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个 main()函数作为程序的入口点,而在驱动开发时却没有 main()函数,模块在调用 insmod 命令时被加载,此时的入口点是 init_module()函数,通常在该函 数中完成设备的注册。同样,模块在调用 rmmod 命令时被卸载,此时的入口点是 cleanup_module() 函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,如 open()、read()、write()等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作,init_module()入口点函数则不需要完成其他如 read()、write() 之类功能。

image-20230218153111422

1、重要数据结构

1.1文件操作接口结构体file_operation

  • 用户应用程序调用设备的一些功能是在设备驱动程序中定义的,也就是设备驱动程序的入口点,它是一个在内核中定义的 struct file_operations 结构,不会出现在用户空间的程序中,但它定义了常见文件 I/O 函数的入口,如下所示:

    struct file_operations 
    { 
        loff_t (*llseek) (struct file *, loff_t, int); 
        ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp); 
        ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp); 
        int (*readdir) (struct file *, void *, filldir_t); 
        unsigned int (*poll) (struct file *, struct poll_table_struct *); 
        int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long arg); 
        int (*mmap) (struct file *, struct vm_area_struct *); 
        int (*open) (struct inode *, struct file *); 
        int (*flush) (struct file *); 
        int (*release) (struct inode *, struct file *); 
        int (*fsync) (struct file *, struct dentry *); 
        int (*fasync) (int, struct file *, int); 
        int (*check_media_change) (kdev_t dev); 
        int (*revalidate) (kdev_t dev); 
        int (*lock) (struct file *, int, struct file_lock *); 
    }; 
    
    • 系统调用函数通过内核,最终调用对应的 struct file_operations 结构的接口函数(例如,open()文件操作是通过调用对应文件的 file_operations 结构的 open 函数接口而被实现)。当然,每个设备的驱动程序不一定要实现其中所有的函 数操作,若不需要定义实现时,则只需将其设为 NULL 即可。

    • 打开设备open()

      • 作用:根据设备的不同,open 函数接口完成的功能也有所不同,但通常情况下在 open 函数接口中要完成如下工作:
        • 递增计数器(MOD_INC_USE_COUNT:计数器加 1,最新版本已经不再使用该宏)。由于设备在使用时通常会打开多次,也可以由不同的进程所使用,所以若有一进程想要删除该设备,则必须保证其他设备没有使用该设备。因此使用计数器就可以很好地完成 这项功能。
        • 如果未初始化,则进行初始化。
        • 识别次设备号,如果必要,更新 f_op 指针。
        • 分配并填写被置于 filp->private_data 的数据结构。
      • 若open()被指定为 NULL,那么设备的打开操作将永远成功,但系统不会通知驱动程序。
    • 释放设备release()

      • 注意:注意释放设备和关闭设备是完全不同的。当一个进程释放设备时,其 他进程还能继续使用该设备,只是该进程暂时停止对该设备的使用;而当一个进程关闭设备时,其他进程 必须重新打开此设备才能使用它。释放设备完成的工作包括:
        • 递减计数器 MOD_DEC_USE_COUNT(最新版本已经不再使用)。
        • 释放打开设备时系统所分配的内存空间(包括 filp->private_data 指向的内存空间)。
        • 若本次是最后一次释放设备操作,则关闭设备。
    • 读写设备read()、write()

      • 作用:把内核空间的数据复制到用户空间,或者从用户空间复制到内核空间,也就是将 内核空间缓冲区里的数据复制到用户空间的缓冲区中或者相反。

      • 注意:虽然这个过程看起来很简单,但是内核空间地址和应用空间地址是有很大区别的,其中一个区别是用户空间的内存是可以被换出的,因此可能会出现页面失效等情况。所以不能使用诸如 memcpy() 之类的函数来完成这样的操作。在这里要使用 copy_to_user()或 copy_from_user()等专门实现用户空间和内核空间的数据交换的函数。这两个函数不仅实现了用户空间和内核空间的数据转换,而且还会检查用户空间指针的有效性。 如果指针无效,那么就不进行复制。

        /* 位于<asm/uaccess.h> */
        unsigned long copy_to_user(void *to, const void *from, unsigned long count); 
        unsigned long copy_from_user(void *to, const void *from, unsigned long count); 
        
        /*
        参数:
        	to:数据目的缓冲区
        	from:数据源缓冲区
        	count:数据长度
        返回值:
        	成功:写入的数据长度
        	失败:-EFAULT
        */
        
    • 控制设备ioctl()

      • 作用:提供对设备的非读写操作机制,例如设置串口设备的波特率等硬件配置和控制函数。

1.2 文件结构体file

  • struct inode 结构提供了关于设备文件/dev/driver(假设此设备名为 driver)的信息,struct file 结构提供关于被打开的文件信息,主要用于与文件系统对应的设备驱动程序使用。struct file 结构较为重要,这里列出了 它的定义:

    struct file 
    { 
        mode_t	f_mode;		/*标识文件是否可读或可写,FMODE_READ 或 FMODE_WRITE*/ 
        dev_t	f_rdev; 	/* 用于/dev/tty */ 
        off_t	f_pos; 		/* 当前文件位移 */ 
        unsigned short f_flags; 	/* 文件标志,如 O_RDONLY、O_NONBLOCK 和 O_SYNC */ 
        unsigned short f_count; 	/* 打开的文件数目 */ 
        unsigned short f_reada; 
        struct inode  *f_inode; 		/*指向 inode 的结构指针 */ 
        struct file_operations *f_op;	/* 文件操作函数索引指针 */ 
    }; 
    

1.3 字符设备结构体char_device_struct

  • 在Linux2.4以前,内核中所有已分配的字符设备编号都记录在一个名为 chrdevs ,元素个数为255的散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,代表主设备号相同的一组设备。它在内核中的定义如下:

    static struct char_device_struct {
           struct char_device_struct *next;    // 指向散列表中的下一个指针
           unsigned int major;                 // 主设备号
           unsigned int baseminor;             // 起始次设备号
           int minorct;                        // 设备编号数
           char name[64];                      // 设备驱动名
           struct file_operations *fops;       // 指向该设备对应的文件操作函数结构体指针
           struct cdev *cdev;                  // 指向字符设备驱动程序描述符的指针
    } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
    

2、设备号

2.1 作用

  • 设备号有主设备号和次设备号,其中主设备号表示设备类型,对应于确定的驱 动程序,具备相同主设备号的设备之间共用同一个驱动程序,而用次设备号来标识具体物理设备。 因此在创建字符设备之前,必须先获得设备的编号(可能需要分配多个设备号)。
  • 在 Linux 2.6 的版本中,用 dev_t 类型来描述设备号(dev_t 是 32 位数值类型,其中高 12 位表示主设备号, 低 20 位表示次设备号)。用两个宏 MAJORMINOR 分别获得 dev_t 设备号的主设备号和次设备号,而 且用 MKDEV 宏来实现逆过程,即组合主设备号和次设备号而获得 dev_t 类型设备号。

2.2 设备号的分配

  • 分配设备号有静态动态的两种方法:

    • 静态分配(register_chrdev_region())是指在事先知道设备主设备号的情况下,通过参数函数指定第一个设备号(它的次设备号通常为 0)而向系统申请分配一定数目的设 备号。
    • 动态分配(alloc_chrdev_region())是指通过参数仅设置第一个次设备号(通常为 0,事先不会知道主设备号)和要分配的设备数目而系统动态分配所需的设备号。
  • 设备号的释放:通过unregister_chrdev_region()释放已分配的(无论是静态的还是动态的)设备号。

  • 它们的函数格式如下所示:

    /* 静态分配:*/
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    {
       struct char_device_struct *cd;
       dev_t to = from + count;
       dev_t n, next;
    
       for (n = from; n < to; n = next) {
           next = MKDEV(MAJOR(n)+1, 0);
           if (next > to)
               next = to;
           cd = __register_chrdev_region(MAJOR(n), MINOR(n), next - n, name);
           if (IS_ERR(cd))
               goto fail;
       }
       return 0;
    fail:
       to = n;
       for (n = from; n < to; n = next) {
           next = MKDEV(MAJOR(n)+1, 0);
           kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
       }
       return PTR_ERR(cd);
    }
    
    /* 动态分配:*/
    int alloc_chrdev_region (dev_t *dev,\
                             unsigned int firstminor, unsigned int count, char *name);
    
    /* 释放 */
    void unregister_chrdev_region (dev_t first, unsigned int count);
    
    
    /*
    from: 要分配的设备号的初始值,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0;
    count:要分配(释放)的设备号数目,比如: 起始次设备号为0,count=100,表示0~99的次设备号都要绑定在同一个file_operations操作方法结构体上.
    name:要申请设备号的设备名称(在/proc/devices 和 sysfs 中显示)
    dev:动态分配的第一个设备号
    
    成功:0(只限于两种注册函数)
    出错:-1(只限于两种注册函数)
    */
    

3、字符设备的注册/注销

  • 在获得了系统分配的设备号之后,通过注册设备才能实现设备号和驱动程序之间的关联。在内核中,使用 struct cdev 结构来描述字符设备,我们在驱动程序中必须将已分配到的设备号以及设备操作接口( struct file_operations 结构)赋予 struct cdev 结构变量

3.1 早期版本的字符设备注册/注销函数

  • 在Linux2.4内核以前使用的是这种分配设备编号范围的函数:register_chrdev()unregister_chrdev()。它每次都是粗粒度的分配一个主设备号和256个(0 ~ 255)次设备号(如果申请的主设备号为 0 则动态分配一个),如果执行成功,设备名就会出现在/proc/devices 文件里。

  • 该函数内部自动分配了一个 cdev 结构,我们另外还需传入一个 file_operations 结构的指针,用来和新建的char_device_struct 结构体绑定,以后凡是相同主设备号(即所有256个共享该主设备号的次设备号设备)的设备均使用同一个file_operations,不管你实际使用了几个次设备号,默认都会将相应主设备号下的256个次设备号连续注册,造成了极大的浪费。

  • 其定义位于头文件 <linux/fs.h> ,详情如下:

    int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
    {
       struct char_device_struct *cd;
       struct cdev *cdev;
       char *s;
       int err = -ENOMEM;
    
       cd = __register_chrdev_region(major, 0, 256, name);
       if (IS_ERR(cd))
           return PTR_ERR(cd);
    
       cdev = cdev_alloc();
       if (!cdev)
           goto out2;
    
       cdev->owner = fops->owner;
       cdev->ops = fops;
       kobject_set_name(&cdev->kobj, "%s", name);
       for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
           *s = '!';
    
       err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
       if (err)
           goto out;
    
       cd->cdev = cdev;
    
       return major ? 0 : cd->major;
    out:
       kobject_put(&cdev->kobj);
    out2:
       kfree(__unregister_chrdev_region(cd->major, 0, 256));
       return err;
    }
    
    int unregister_chrdev(unsigned int major, const char *name);
    
    /*
    参数:
    	major:设备驱动程序向系统申请的主设备号,如果为 0 则系统为此驱动程序动态地分 配一个主设备号。
        name:设备名
        fops:对各个调用的入口点
    返回值:
    	成功:0,如果是动态分配主设备号,此返回所分配的主设备号。且设备名就会出现在/proc/devices 文件里。
    	失败:-1
    

3.2 最新版本的字符设备注册注销函数

  • 函数原型:位于头文件<linux/cdev.h>中:

    sturct cdev *cdev_alloc(void); 
    void cdev_init(struct cdev *cdev, struct file_operations *fops);
    int cdev_add (struct cdev *cdev, dev_t num, unsigned int count);
    void cdev_del(struct cdev *dev);
    /*
    参数:
    	cdev:需要初始化/注册/删除的 struct cdev 结构
    	fops:该字符设备的 file_operations 结构
    	num:系统给该设备分配的第一个设备号(动态或静态方式取得的)
    	count:该设备对应的设备号数量
    返回值:
    	成功:
    		cdev_alloc:返回分配到的 struct cdev 结构指针
    		cdev_add:返回 0 
    	出错:
    		cdev_alloc:返回 NULL 
    		cdev_add:返回 -1 
    */
    
  • 字符设备注册流程

    • 首先使用 cdev_alloc() 函数向系统申请分配 struct cdev 结构;
    • 再用 cdev_init()函数初始化已分配到的结构并与 file_operations 结构关联起来。
    • 最后调用 cdev_add()函数将设备号与 struct cdev 结构进行关联,并向内核正式报告新设备的注册,这样新设备可以被用起来了。
    • 创建一种类,即创建**/proc/class/xxx**。例如cls=class_create(THIS_MODULE, "hello");
    • 创建字符设备节点,即创建**/dev/xxx_x**。例如class_device_create(cls,0, MKDEV(major,0), 0, "hello_1");
  • 使用示例:在同一主设备号下创建两个设备域,并分别与不同的文件操作函数绑定。

    /*
     *创建两个字符设备,他们公用同一个主设备号;
     *但次设备号0~1对应第一个字符设备,使用hello1_fops文件操作符;
     * 次设备号2~3对应第二个字符设备,使用hello2_fops文件操作符;
     * 次设备号4不对应字符设备,不使用文件操作符;
     */
    
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/irq.h>
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
    #include <asm/uaccess.h>
    #include <asm/io.h>
    #include <linux/list.h>
    #include <linux/cdev.h>
    
    static int hello_fops1_open(struct inode *inode, struct file *file)
    {
        printk("open_hello1\n");
        return 0;
    }
    
     
    
    static int hello_fops2_open (struct inode *inode, struct file *file)
    {
        printk("open_hello2\n");
        return 0;
    }
    
     
      /*  操作结构体1   */
    static struct file_operations hello1_fops={
            .owner=THIS_MODULE,
            .open =hello_fops1_open,
    };
    
      /*  操作结构体2   */
    static struct file_operations hello2_fops={
            .owner=THIS_MODULE,
            .open =hello_fops2_open,
    };
    
     
    static int major;                                 //主设备
    static struct cdev hello1_cdev;        //保存 hello1_fops操作结构体的字符设备 
    static struct cdev hello2_cdev;         //保存 hello2_fops操作结构体的字符设备 
    static struct class *cls;
    
    static int chrdev_region_init(void)
    {
         dev_t  devid;  
    #if 0
        major = register_chrdev(0,"hello",&hello_fops);	//以前采用这种形式
    #else
    	 if(major)
         {
             devid = MKDEV(major,0);
             register_chrdev_region(devid, 2, "hello");	   
         }else
         {
         	 alloc_chrdev_region(&devid, 0, 2,"hello");    //动态分配字符设备
         	 major=MAJOR(devid);
         }    
         cdev_init(&hello1_cdev, &hello1_fops);		//初始化cdev,绑定fops结构体
         cdev_add(&hello1_cdev, MKDEV(major,0), 2); //注册cdev,即绑定(major,0~1)
    	 
         devid = MKDEV(major,2);
    	 register_chrdev_region(devid, 2, "hello2");	
         cdev_init(&hello2_cdev, &hello2_fops);
         cdev_add(&hello2_cdev,devid, 2);  //注册cdev,即绑定(major,2~3)
    #endif
    
         cls=class_create(THIS_MODULE, "hello");
         /*创建字符设备节点*/
         class_device_create(cls,0, MKDEV(major,0), 0, "hello0");   //对应hello_fops1操作结构体
         class_device_create(cls,0, MKDEV(major,1), 0, "hello1");   //对应hello_fops1操作结构体
         class_device_create(cls,0, MKDEV(major,2), 0, "hello2");   //对应hello_fops2操作结构体
         class_device_create(cls,0, MKDEV(major,3), 0, "hello3");   //对应hello_fops2操作结构体
         class_device_create(cls,0, MKDEV(major,4), 0, "hello4");   //对应空
         
         return 0;
    }
    
    void chrdev_region_exit(void)
    {
       class_device_destroy(cls, MKDEV(major,4));
       class_device_destroy(cls, MKDEV(major,3));
       class_device_destroy(cls, MKDEV(major,2));
       class_device_destroy(cls, MKDEV(major,1));
       class_device_destroy(cls, MKDEV(major,0));
    
       class_destroy(cls);
    
    
       cdev_del(&hello1_cdev);   
       unregister_chrdev_region(MKDEV(major,0), 2);     //注销(major,0)~(major,1)
        
       cdev_del(&hello2_cdev); 
       unregister_chrdev_region(MKDEV(major,2), 2);     //注销(major,2)~(major,3)
    } 
    
    module_init(chrdev_region_init);
    module_exit(chrdev_region_exit);
    MODULE_LICENSE("GPL");
    
    • 针对以上驱动,编写如下测试程序:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    void print_useg(char arg[])    //打印使用帮助信息
    {
             printf("useg:  \n");
             printf("%s   [dev]\n",arg);
    }
    
    int main(int argc,char **argv)
    {
    
      int fd;
      if(argc!=2)
        {
            print_useg(argv[0]);
            return -1;
        }
    
      fd=open(argv[1],O_RDWR);
      if(fd<0)
          printf("can't open %s \n",argv[1]);
      else
          printf("can open %s \n",argv[1]);
      return 0;
    }
    
    • 装载驱动后,进行测试,得到如下结果:

      # ls /dev/hello* -l
      crw-rw----	1 0		0		252,	0 Jan	1 00:12  /dev/hello0
      crw-rw----	1 0		0		252,	1 Jan	1 00:12  /dev/hello1
      crw-rw----	1 0		0		252,	2 Jan	1 00:12  /dev/hello2
      crw-rw----	1 0		0		252,	3 Jan	1 00:12  /dev/hello3
      crw-rw----	1 0		0		252,	4 Jan	1 00:12  /dev/hello4
      
      #./a.out /dev/hello0	//打开/dev/hello0时,调用的是hello1_fops里的.open()
      open hello0
      #
      #
      #./a.out /dev/hello2  //打开/dev/hello2时,调用的是hello1_fops里的.open()
      open hello2
      #
      #
      #./a.out /dev/hello4  //打开无效,因为在驱动代码里没有分配次设备号4的操作结构体
      can't open /dev/hello4
      

4、知识拓展

4.1 设备号分配原理

起始不管是静态还是动态分配,内核中设备号的分配最终都是调用 __register_chrdev_region()函数实现的,首先来看一下__register_chrdev_region函数的定义:

static struct char_device_struct * __register_chrdev_region(unsigned int major, 		unsigned int baseminor, int minorct, const char *name)
{
   struct char_device_struct *cd, **cp;
   int ret = 0;
   int i;
   cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
   if (cd == NULL)
       return ERR_PTR(-ENOMEM);
   mutex_lock(&chrdevs_lock);
   if (major == 0) {
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--)
           if (chrdevs[i] == NULL)
               break;
       if (i == 0) {
           ret = -EBUSY;
           goto out;
       }
       major = i;
       ret = major;
   }
   cd->major = major;
   cd->baseminor = baseminor;
   cd->minorct = minorct;
   strncpy(cd->name,name, 64);
   i = major_to_index(major);
   for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
       if ((*cp)->major > major ||
            ((*cp)->major == major && 
            ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) ))
           break;
   /* Check for overlapping minor ranges. */
   if (*cp && (*cp)->major == major) {
       int old_min = (*cp)->baseminor;
       int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
       int new_min = baseminor;
       int new_max = baseminor + minorct - 1;

       /* New driver overlaps from the left. */
       if (new_max >= old_min && new_max <= old_max) {
           ret = -EBUSY;
           goto out;
       }
       /* New driver overlaps from the right. */
       if (new_min <= old_max && new_min >= old_min) {
           ret = -EBUSY;
           goto out;
       }
   }
   cd->next = *cp;
   *cp = cd;
   mutex_unlock(&chrdevs_lock);
   return cd;
out:
   mutex_unlock(&chrdevs_lock);
   kfree(cd);
   return ERR_PTR(ret);
}
  • 函数 __register_chrdev_region() 主要执行以下步骤:
  1. 分配一个新的 char_device_struct 结构,并用 0 填充。
  2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,如果那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
  3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
  4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。如果设备编号范围有重复的话,则出错返回。
  5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。

4.2 驱动程序中常用的内核函数

  • 内存分配/释放

    • 在应用程序中获取内存通常使用函数 malloc(),但在设备驱动程序中动态开辟内存可以以字节或页面为单 位。其中,以字节为单位分配内存的函数有 kmalloc(),注意的是,kmalloc()函数返回的是物理地址,而 malloc()等返回的是线性虚拟地址,因此在驱动程序中不能使用 malloc()函数。

    • 与 malloc()不同,kmalloc() 申请空间有大小限制。长度是 2 的整次方,并且不会对所获取的内存空间清零。以页为单位分配内存的函数:

      • get_zeroed_page():获得一个已清零页面。
      • get_free_page():获得一个或几个连续页面。
      • get_dma_pages():获得用于 DMA 传输的页面。
    • 与之相对应的释放内存用也有 kfree()或 free_page 函数族。

    • 基于字节的内存分配函数kmalloc()

      • 所在头文件:<linux/malloc.h>
      • 原型:void *kmalloc(unsigned int len,int flags)
      • 参数:
        • len:希望申请的字节数
        • flags:
          • GFP_KERNEL:内核内存的通常分配方法,可能引起睡眠;
          • GFP_BUFFER:用于管理缓冲区高速缓存;
          • GFP_ATOMIC:为中断处理程序或其他运行于进程上下文之外的代码分配内存,且不会引起睡眠;
          • GFP_USER:用户分配内存,可能引起睡眠;
          • GFP_HIGHUSER:优先高端内存分配;
          • __GFP_DMA:DMA 数据传输请求内存;
          • __GFP_HIGHMEN:请求高端内存。
        • 返回值:
          • 成功:写入的数据长度
          • 失败:-EFAULT
    • 基于字节的内存释放函数kfree()

      • 所在头文件:<linux/malloc.h>
      • 原型:void kfree(void *obj)
      • 参数:obj为要释放的内存指针
      • 返回值:
        • 成功:释放的数据长度
        • 失败:-EFAULT
    • 基于页的内存分配函数get_free_page()族函数

      • 头文件:<linux/malloc.h>

      • 原型:

        unsigned long get_zeroed_page(int flags) 
        unsigned long __get_free_page(int flags) 
        unsigned long __get_free_page(int flags,unsigned long order) 
        unsigned long __get_dma_page(int flags,unsigned long order) 
        
      • 参数:

        • flags:同kmalloc
        • order:要请求的页面数,以 2 为底的对数
      • 返回值:

        • 成功:指向新分配的页面的指针
        • 失败:-EFAULT
    • 基于页的内存释放函数 free_ page 族函数

      • 头文件:<linux/malloc.h>

      • 原型:

        unsigned long free_page(unsigned long addr) 
        unsigned long free_pages(unsigned long addr, unsigned long order) 
        
      • 参数:

        • addr:要释放的内存起始地址
        • order:请求释放的页面数,以 2 为底的对数
      • 返回值:

        • 成功:释放的数据长度
        • 失败:-EFAULT
  • 信息打印

    • 与用户空间不同,在内核空间要用函数 printk()而不能用平常的函数 printf()。printk()和 printf()很类似, 都可以按照一定的格式打印消息,所不同的是,printk()还可以定义打印消息的优先级。这些不同优先级的信息输出到系统日志文件(例如:“/var/log/messages”),有时也可以输出到虚拟控制台 上。其中,对输出给控制台的信息有一个特定的优先级 console_loglevel。只有打印信息的优先级小于这个 整数值,信息才能被输出到虚拟控制台上,否则,信息仅仅被写入到系统日志文件中。若不加任何优先级选项,则消息默认输出到系统日志文件中。
    • 要开启 klogd 和 syslogd 服务,消息才能正常输出。
    • 内核打印函数printk()
      • 头文件:<linux/kernel.h>
      • 原型:int printk(const char *fmt,...)
      • 参数:
        • fmt:日志级别
          • KERN_EMERG:紧急时间消息;
          • KERN_ALERT:需要立即采取动作的情况;
          • KERN_CRIT:临界状态,通常涉及严重的硬件或软件操作失败;
          • KERN_ERR:错误报告;
          • KERN_WARNING:对可能出现的问题提出警告;
          • KERN_NOTICE:有必要进行提示的正常情况;
          • KERN_INFO:提示性信息;
          • KERN_DEBUG:调试信息
        • …:与 printf()相同;
      • 返回值:
        • 成功:0
        • 失败:-1
  • proc文件系统

4.3 proc文件系统

  • /proc 文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统可以让用户可以和内核内部数据结构进行交互,获取有关系统和进程的有用信息,在运行时通过改变内核参数来改变设置。与其他文件系统不同,/proc 存在于内存之中而不是在硬盘上,可以通过“ls”查看/proc 文件系统的内容。

  • 下图列出了/proc 文件系统的主要目录内容:

    image-20230218174913736

  • 还有一些是以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一 个目录在/proc 下,以进程的 PID 号为目录名,它们是读取进程信息的接口。进程目录的结构如下

    image-20230218175013380

  • 可以看到,/proc 文件系统体现了内核及进程运行的内容,在加载模块成功后,读者可以通过查看/proc/device 文件获得相关设备的主设备号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值