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

news2024/11/15 21:22:03

概述

  • 设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个 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 文件获得相关设备的主设备号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356331.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JVM学习笔记一:类加载子系统

目录 前言 类加载子系统的作用 类加载器角色的位置 类加载器分类 虚拟机自带的加载器 启动类加载器&#xff08;引导类加载器&#xff09; 扩展类加载器 系统类加载器 用户自定义类加载器 什么时候需要自定义类加载器&#xff1f; 如何自定义类加载器&#xff1f; …

【验证码的识别】—— 极验验证码的识别

前言 &#xff08;结尾有彩蛋欧&#xff09; 目前&#xff0c;许多网站采取各种各样的措施来反爬虫&#xff0c;其中一个措施便是使用验证码。随着技术的发展&#xff0c;验证码的花样越来越多。验证码最初是几个数字组合的简单的图形验证码&#xff0c;后来加入了英文字母和混…

《计算机系统基础》——计算机系统导论

文章目录《计算机系统基础》——计算机系统导论计算机的基本组成程序开发与执行过程机器语言汇编语言高级语言程序的转换处理程序的数据流动计算机系统层次结构早期计算机系统1GL2GL现代计算机系统3GL4GL指令集体系结构《计算机系统基础》——计算机系统导论 &#x1f680;接下…

LaTeX中表格过宽解决方案

最近使用LaTeX处理表格时遇到了一件十分棘手的问题&#xff0c;由于内容较多将表格分成了好多列&#xff0c;但将内容填入表格时由于表格宽度过大&#xff0c;导致表格右边溢出了页面无法查看&#xff0c;查阅大量资料与博文后给出如下解决方案&#xff0c;全文代码已部署在Ove…

C#基础练习题,编程题汇总

C#基础练习题&#xff0c;编程题汇总一、C#提取输入的最大整数二、秒数换算为相应的时、分、秒三、C#计算电梯运行用时demo四、C#用一维数组求解问题五、C#程序教小学生学乘法六、C#winfrm简单例题七、C#类继承习题八、C#绘图例子一、C#提取输入的最大整数 编程实现在一行内输…

分布式任务调度(XXL-JOB)

什么是分布式任务调度&#xff1f; 任务调度顾名思义&#xff0c;就是对任务的调度&#xff0c;它是指系统为了完成特定业务&#xff0c;基于给定时间点&#xff0c;给定时间间隔或者给定执行次数自动执行任务。通常任务调度的程序是集成在应用中的&#xff0c;比如&#xff1a…

[译]PostgreSQL16-新特性-新增IO统计视图:pg_stat_io

PostgreSQL16-新特性-新增IO统计视图&#xff1a;pg_stat_io我们DBA常遇到的问题是&#xff1a;如何优化数据库的IO操作&#xff1f;获取PG服务产生的所有IO情况历来都是一个挑战。首先&#xff0c;PG将IO行为范围内为写WAL和读写数据目录(也就是数据文件)。真正的挑战是&#…

解决实际项目中stalled时间过久的问题

背景 在公司参与了一个做度量统计的项目&#xff0c;该项目的特点是页面上的表格、卡片、图标非常多。项目经常出现一种情况&#xff1a;页面加载速度较慢&#xff0c;开始怀疑是由于计算量较大&#xff0c;后端接口相应速度较慢。优化了一版后端接口后&#xff08;加缓存、优…

方法区和元空间有什么关系?

一.什么是方法区&#xff1f; 方法区属于是 JVM 运行时数据区域的一块逻辑区域&#xff0c;是各个线程共享的内存区域。 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用&#xff0c;方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说&#xff0c;在…

电子技术——分析放大器的高频响应的有用工具

电子技术——分析放大器的高频响应的有用工具 在前几章我们使用米勒效应估计了CS和CE放大器模型的高频响应 fHf_HfH​ &#xff0c;以及分析了其限制和影响因素。然而&#xff0c;这个方法不能有效的处理负载是容性负载 CLC_LCL​ 的情况。同时&#xff0c;这个方法不能扩展到更…

【FPGA】Verilog:实现十六进制七段数码管显示 | 7-Segment Display

写在前面&#xff1a;本章主要内容为理解七点数码管显示的概念&#xff0c;并使用 Verilog 实现。生成输入信号后通过仿真确认各门的动作&#xff0c;通过 FPGA 检查在 Verilog 中实现的电路的操作。 Ⅰ. 前置知识 七段数码管是利用多重输出功能的非常有用的元件。该元件用于字…

spring+springboot+mybatis志愿者报名系统 ssm java

本盐城疫情防控志愿者报名系统以SSM作为框架&#xff0c;B/S模式以及MySql作为后台运行的数据库。本系统主要包括以下功能模块&#xff1a;防疫视频、优秀事迹、报名条件、在线报名等模块&#xff0c;通过这些模块的实现能够基本满足日常盐城疫情防控的操作。 根据盐城疫情防控…

基于微信小程序的中国各地美食推荐平台小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.…

【js】export default也在影响项目性能呢

这里写目录标题介绍先说结论分析解决介绍 无意间看到一个关于export与exprot default对比的话题&#xff0c; 于是对二者关于性能方面&#xff0c;有了想法&#xff0c;二者的区别&#xff0c;仅仅是在于写法吗&#xff1f; 于是&#xff0c;有了下面的测试。 先说结论 太长…

.NET3.5安装步骤及相关问题。

.NET3.5全称 Microsoft.NETFramework3.5 最新版本-.NET4.8 第一步打开控制面板 windows系统打开控制面板 选择程序 选择.NET3.5安装。 可能会出现问题。 解决方案&#xff1a; 报错代码80240438的常用解决办法&#xff1a; 方法一&#xff1a;检测windows update servic…

【NLP实战】Python字符串处理

一、Python字符串基本操作 1. 去掉前后的特殊字符&#xff08;strip&#xff09; Python的strip操作可以去除字符串前后的空格&#xff08;不改变原串&#xff09;下例将前后的空格均删掉&#x1f447; str 人工智能 str.strip() # OUT:人工智能rstrip删除右边的空格&a…

linux016之安装JDK

linux上安装JDK&#xff1a; 一&#xff1a;首先检查一下linux上是否已经安装有jdk rpm -qa | grep jdk &#xff1a;查询目前系统已安装的jdk&#xff0c;直接复制该命令执行&#xff0c;如下图就是系统已经安装好的JDK rpm -qa | grep -i java | xargs -n1 rpm -e --nodeps &…

线段树(维护区间信息)

一&#xff0c;定义&#xff1a; 可以在logN时间内实现区间修改&#xff0c;单点修改&#xff0c;区间查询等操作的工具 二&#xff0c;思路&#xff08;修改无乘法时&#xff09;&#xff1a; 1&#xff0c;建树 通过把区间不断二分建立一颗二叉树 我们以维护一个数组a{1…

流程引擎之compileflow简介

背景compileflow 是一个非常轻量、高性能、可集成、可扩展的流程引擎。compileflow Process 引擎是淘宝工作流 TBBPM 引擎之一&#xff0c;是专注于纯内存执行&#xff0c;无状态的流程引擎&#xff0c;通过将流程文件转换生成 java 代码编译执行&#xff0c;简洁高效。当前是阿…

JVM内存布局

JVM的主要组成&#xff1a;JVM包含俩个子系统和俩个组件&#xff0c;俩个子系统为Class loader&#xff08;类装载&#xff09;、Execution engine&#xff08;执行引擎&#xff09;&#xff1b;俩个组件为Runtime data area&#xff08;运行时数据区&#xff09;、Native Inte…