代码
linux系统对系统内驱动提供一个调试功能——debugfs。
debugfs是一种通过对用户空间文件进行读写操作来实现驱动和用户空间交互的调试方式。
驱动需要在系统debugfs根目录下创建一个文件,再实现对该文件的读写操作函数。这样在用户空间读写文件时,会调用驱动中的操作函数,就可进行相关的驱动内部操作了,一般用于修改驱动某个变量值或是内部消息的传递。
这个debugfs根目录地址是系统分配的,默认情况下,debugfs会被挂载在目录/sys/kernel/debug之下。cfg80211_init里创建wiphy,wiphy_register(wiphy)调用完了之后,系统会分配一个文件索引节点struct dentry *dentry=wiphy->debugfsdir。在dentry指向的目录里驱动可以创建新的目录或是直接创建文件。
我在驱动里加入以下代码:
struct my_handle *private_hd;//my_handle是驱动自定义结构体,把这个结构体指针在创建文件时就传进去,后期读写文件时会用到这个结构体指针。
struct dentry *sys_dir=wiphy->debugfsdir;//wiphy_register之后,系统分配的根目录会存在debugfsdir成员里
struct dentry *root_drv;
if (!(root_drv= debugfs_create_dir("my_test", sys_dir)))//在系统目录下建一个名为my_test的目录
return -ERR;
if(!debugfs_create_file("number", S_IWUSR | S_IRUSR, root_drv, (void *)private_hd, &number_debugfs_ops) ) //private_hd是驱动的私有数据结构,创建文件number
return -ERR;
这段代码所做的操作是,在系统debug目录下,创建了mytest目录,在mytest里创建文件number,权限可读可写(S_IWUSR 对应写权限,S_IRUSR对应读权限),将操作函数绑定为number_debugfs_ops。
在我Ubuntu里,系统分配的debug目录是/sys/kernel/debug/ieee80211/phy3/,访问这个目录需要root权限,phy后面跟的数字每次insmod驱动都会分配到不同的数字。
debugfs_create_dir可以创建目录:struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)//传入参数列表为(“子目录名字”, struct dentry * 结构变量指向的上一级目录)。
debugfs_create_dir函数里对目录权限的设置:inode->i_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO;
其中内核里的相关define如下(也可在此网址查询各类标识符含义:Index of all identifiers in unit ‘BaseUnix’ (freepascal.org)):
#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
#define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
#define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)
... ...
#define S_IRWXU 00700 //const S_IRWXU = S_IRUSR or S_IWUSR or S_IXUSR;用户读写执行
#define S_IFDIR 0040000 //mode: Directory
#define S_IRUSR 00400 //用户读
#define S_IRGRP 00040 //用户组读
#define S_IROTH 00004 //其他读
#define S_IWUSR 00200 //用户写
#define S_IWGRP 00020 //用户组写
#define S_IWOTH 00002 //其他写
#define S_IXUSR 00100 //用户执行
#define S_IXGRP 00010 //用户组执行
#define S_IXOTH 00001 //其他执行
... ...
如此,知道了debugfs_create_dir里赋予新目录文件的权限是:目录+用户xwr+组xwr+其他xwr
这个权限值就对应着ls -l后各个文件的权限值
再来看debugfs_create_file,它的函数里调用了__debugfs_create_file函数,在__debugfs_create_file里:inode->i_private = data;//data就对应着调用debugfs_create_file时的private_hd指针
这一句将传进去的驱动自定义结构体指针private_hd存到i_private 里,这样子在后续对文件进行读写操作时,将这个i_private 的值传进读、写操作函数,就可在函数内操作private_hd中的值,从而实现修改内核中参数的功能。
在驱动中加入ops操作集如下:
static const struct file_operations number_debugfs_ops = {
.write = number_debugfs_write,
.read = number_debugfs_read,
.open = simple_open,
.llseek = generic_file_llseek,
};
其中file_operations结构:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
... ...
};
在驱动中加入number文件的读写操作函数如下,传入参数表的格式照着file_operations中定义的格式写:
static ssize_t number_debugfs_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos){
struct my_handle *priv=file->private_data;
char buf[32];
int ret;
ssize_t read;
printk("number_read:%d\n", priv->number);
ret=scnprintf(buf, sizeof(buf)-1, "number=%d\n", priv->number);
if(*ppos >= ret) return 0;//这里是为了第二次进入read函数时,如果文件位置指针已经超过了目的指针位置,则说明上次函数已读取字符串并打印到控制台里,直接退出即可
read=copy_to_user(user_buf, (void *)buf, ret);//正常情况均返回0,则read赋值为0
*ppos += ret;//移动文件读取位置指针
return ret;
}
static ssize_t number_debugfs_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos){
struct my_handle *priv=file->private_data;
char buf[32];
int val;
ssize_t len=min_t(size_t, count, sizeof(buf)-1);
if(copy_from_user(buf, user_buf, len))
return -EFAULT;
buf[len]='\0';
if(sscanf(buf, "%d", &val)>0)
priv->number=val;
printk("after number_write:%d\n", priv->number);
return count;
}
其中,读写函数传入参数表里的loff_t *ppos表示的是这次对文件进行操作的起始位置。
运行效果
运行后效果:能看到在指定目录phy3下,成功创建了my_test目录,并在my_test目录里创建了文件number。读写number的时候,会调驱动里的操作函数,能通过dmesg看到对应输出。
不过很神奇的是,经多次实践发现,一次读操作会调两次驱动中的文件读函数,而number_debugfs_read的返回值将决定是否有第二次进入以及是否能将字符串打印到控制台。
- 如果调用它直接返回0,则不会将“number=x”打印到控制台,返回正数值就会再次进入函数;
- 如果每次调用它都返回正数值(例如返回值是ret的值,每次进入函数ret都不可能被赋值为0),则会一直进入此函数形成死循环,在控制台狂打印“number=x”;
- 若第一次调用它返回正数值,第二次进入时,通过条件判断是第二次进入,从而使第二次返回0,即可在控制台打印一次“number=x”,并结束调用
希望以上这部分有懂得大佬能解释下!!!
如果ls -l一下,能看到:
root@vostro:/sys/kernel/debug/ieee80211/phy3/my_test# ls -l
总用量 0
-rw------- 1 root root 0 8月 26 20:50 number
表示文件的拥有者root对文件有读写权限,组用户和其他用户没有任何权限
TIPS
正好我有写过如何在一个应用层软件代码里获取当前wifi驱动对应的debugfs目录phy值,分享一下(前提是指定wlan1)
获取每次驱动对应的phy值:
int GetPhy(void)
{
int phy=0;
FILE *tmpfd;
char string[256];
char* str;
CMD_EXEC("iw %s info | grep wiphy | cut -f2 -dw > /tmp/tmp_phy.txt", WFA_STAUT_IF);//WFA_STAUT_IF是一个全局变量,指定了wifi驱动的网口为wlan1
tmpfd = fopen("/tmp/tmp_phy.txt", "r+");
if(tmpfd == NULL) { return FAILURE;}
for(;;){
if(fgets(string, 256, tmpfd) == NULL)
break;
if(strncmp(string, "iphy", 4) ==0)
{
DPRINT_INFO("--> Found phy info \n");
str = strtok(string, " ");
DPRINT_INFO("1:%s\n", str);
str = strtok(NULL, " ");
DPRINT_INFO("2:%s\n", str);
if(str != NULL){
DPRINT_INFO("PHYID=%s\n", str);
phy = atoi(str);
}
else
phy = 0;
}
}
DPRINT_INFO("phyid == %d--\n", phy);
return phy;
}
其实原理就是一句命令:
len@vostro:~$ iw wlan1 info | grep wiphy
wiphy 3
len@vostro:~$