概述
- 设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个 main()函数作为程序的入口点,而在驱动开发时却没有 main()函数,模块在调用 insmod 命令时被加载,此时的入口点是
init_module()
函数,通常在该函 数中完成设备的注册。同样,模块在调用 rmmod 命令时被卸载,此时的入口点是cleanup_module()
函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,如 open()、read()、write()等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作,init_module()入口点函数则不需要完成其他如 read()、write() 之类功能。
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,那么设备的打开操作将永远成功,但系统不会通知驱动程序。
- 作用:根据设备的不同,open 函数接口完成的功能也有所不同,但通常情况下在 open 函数接口中要完成如下工作:
-
释放设备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 位表示次设备号)。用两个宏 MAJOR 和 MINOR 分别获得 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() 主要执行以下步骤:
- 分配一个新的 char_device_struct 结构,并用 0 填充。
- 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,如果那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
- 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
- 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。如果设备编号范围有重复的话,则出错返回。
- 将新的 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()相同;
- fmt:日志级别
- 返回值:
- 成功:0
- 失败:-1
-
proc文件系统
4.3 proc文件系统
-
/proc 文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统可以让用户可以和内核内部数据结构进行交互,获取有关系统和进程的有用信息,在运行时通过改变内核参数来改变设置。与其他文件系统不同,/proc 存在于内存之中而不是在硬盘上,可以通过“ls”查看/proc 文件系统的内容。
-
下图列出了/proc 文件系统的主要目录内容:
-
还有一些是以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一 个目录在/proc 下,以进程的 PID 号为目录名,它们是读取进程信息的接口。进程目录的结构如下
-
可以看到,/proc 文件系统体现了内核及进程运行的内容,在加载模块成功后,读者可以通过查看/proc/device 文件获得相关设备的主设备号。