关于字符设备驱动的通用概念和写法
创始人
2024-05-26 06:41:22
0

概述

  • 设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个 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()等专门实现用户空间和内核空间的数据交换的函数。这两个函数不仅实现了用户空间和内核空间的数据转换,而且还会检查用户空间指针的有效性。 如果指针无效,那么就不进行复制。

        /* 位于 */
        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个次设备号连续注册,造成了极大的浪费。

  • 其定义位于头文件 ,详情如下:

    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 最新版本的字符设备注册注销函数

  • 函数原型:位于头文件中:

    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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 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 0major = register_chrdev(0,"hello",&hello_fops);	//以前采用这种形式
    #elseif(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)
    #endifcls=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 
    #include 
    #include 
    #include 
    #include 
    #include 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]);elseprintf("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()

      • 所在头文件:
      • 原型: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()

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

      • 头文件:

      • 原型:

        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 族函数

      • 头文件:

      • 原型:

        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()
      • 头文件:
      • 原型: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 文件获得相关设备的主设备号。

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...