目录
🍅点击这里查看所有博文
随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。
想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。
很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。
同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。
既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来
,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。
本系列博客所述资料均来自互联网
,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法
。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。
设备文件和硬件设备的关系
Linux的应用层中的设备文件(/dev/testcom1)可通过mknod创建,每个设备文件对应一个设备。也可以多个文件对应一个设备。
root@ubuntu:# mknod /dev/testcom1 c 237 0
root@ubuntu:# mknod /dev/testcom2 c 237 0
root@ubuntu:# mknod /dev/testcom3 c 237 0
root@ubuntu:# ls /dev/testcom
testcom1 testcom2 testcom3
创建设备文件时设备可以不存在,上面的示例创建了三个设备文件,实际该文件的驱动并没有没加载。这些设备文件在open时,会报错No such device or address。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main(void)
{int fd;fd = open("/dev/testcom1",O_RDWR);if(fd < 0){perror("open fail");return;}
}
root@ubuntu:# ./test
open fail : No such device or address
应用层的每个设备文件都有一个inode结构体用于维护设备文件的基本信息,也就是主设备号和次设备号。
驱动层中通过char_device_struct中记录的设备号找到对应的硬件设备。
驱动如何支持多个设备
原理
通过下面列出的示例,细心一点的小伙伴们或许已经注意到了应用层在中的打开文件时会返回一个fd。我们一般将其称之为文件的句柄,后续针对该文件的读写操作都将通过句柄完成,设备所对应的驱动文件将不再被使用到。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h> //close
void main(void)
{int len = 0;int fd = open("/dev/hellodev",O_RDWR);if(fd < 0) {perror("open fail\n");return;}char buf[64 + 1] = {0};len = read(fd, buf, sizeof(buf) - 1);buf[len] = '\0';printf("read:%s,len = %d\n", buf, len);char buf2[64 + 1] = "device write test";len = write(fd, buf2, strlen(buf2));printf("write ok,len=%d\n", len);close(fd);return;
}
我们所编写的驱动却只有唯一的函数,那驱动该如何区分不同设备的访问请求呢。通过对比驱动的入参和应用程序的入参,我们也不难猜出应用层open
一个设备时,其所对应的设备号将会通过inode
参数传入到驱动中,而进行读写操作时文件句柄所携带的信息也将会通过file
参数输入到驱动中。
static int dev_fifo_open (struct inode *inode, struct file *file)
{xxxxxx
}ssize_t dev_fifo_read (struct file *file, char __user *buf, size_t size, loff_t *pos)
{xxxxxx
}
前面我们学习vfs层的时候,实际上就已经列出了inode的结构,其中i_rdev成员中所记录的信息就是当前操作设备的设备号。
//include/linux/fs.h
struct inode {// 记录设备的类型umode_t i_mode;xxxxxxxxxxxxx// 记录文件所对应的设备号dev_t i_rdev;xxxxxxxxxxxxxunion {xxxxxxxxxxxxx// 记录描述字符设备的结构体struct cdev *i_cdev;};
};
而针对file结构图,其中有一个指针变量private_data
。我们可以通过该指针变量,将当前的file与设备号绑定起来。只要当前的file(fd)中保存了设备号信息,那么我们在后续读写的过程中就可以直接找到该设备进行操作。
struct file {//include/linux/fs.hxxxxxxxxxxxxx//记录字符设备的操作函数const struct file_operations *f_op;xxxxxxxxxxxxx//文件指针偏移值loff_t f_pos;xxxxxxxxxxxxx/* needed for tty driver, and maybe others */void *private_data;
};
实现
定义一个全局变量的结构体数组,用于保存不同设备的设备号以及一些其他的数据。
#define MAX_COM_NUM 2
struct mydev{dev_t devno;struct device *class_dev;int data;
};
struct mydev pmydev[MAX_COM_NUM] = {0};
针对open
接口,只需通过MINOR(inode->i_rdev)
计算出设备的次设备号,然后找到该设备号说对应的pmydev
成员。这边简单处理就是直接将pmydev
成员赋值给private_data
。
static int dev_fifo_open (struct inode *inode, struct file *file)
{struct mydev *private_data;int pdev_index = 0;printk("dev_fifo_open minor = %d \n",MINOR(inode->i_rdev));pdev_index = MINOR(inode->i_rdev) - minor;private_data = &pmydev[pdev_index];file->private_data = private_data;return 0;
}
而read
接口,便可直接通过file
的private_data
成员,找到所访问设备的上下文变量pmydev
。
ssize_t dev_fifo_read (struct file *file, char __user *buf, size_t size, loff_t *pos)
{struct mydev *private_data;private_data = (struct mydev *)file->private_data;printk("read() file->private_data private_data->data=%d\n",private_data->data);if(copy_to_user(buf, &(private_data->data), size)){return -EFAULT;}return size;
}
实验代码
驱动代码如下,需要注意的是,在驱动模块加载时就要完成所支持的从设备上下文的相关变量的赋值,包括但不局限于devno
,class_dev
。在驱动模块退出时会使用到该变量,用以完成相关资源的释放。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
static const int major = 237;
static const int minor = 5;
struct class *cls;#define MAX_COM_NUM 2
struct mydev{dev_t devno;struct device *class_dev;int data;
};
struct mydev pmydev[MAX_COM_NUM] = {0};ssize_t dev_fifo_read (struct file *file, char __user *buf, size_t size, loff_t *pos)
{struct mydev *private_data;private_data = (struct mydev *)file->private_data;printk("read() file->private_data private_data->data=%d\n",private_data->data);if(copy_to_user(buf, &(private_data->data), size)){return -EFAULT;}return size;
}
int dev_fifo_close (struct inode *inode, struct file *file)
{printk("dev_fifo_close()\n");return 0;
}
static int dev_fifo_open (struct inode *inode, struct file *file)
{struct mydev *private_data;int pdev_index = 0;printk("dev_fifo_open minor = %d \n",MINOR(inode->i_rdev));pdev_index = MINOR(inode->i_rdev) - minor;private_data = &pmydev[pdev_index];file->private_data = private_data;return 0;
}
static struct file_operations hello_ops =
{.open = dev_fifo_open,.read = dev_fifo_read,.release = dev_fifo_close,
};
static int hello_init(void)
{int result;int dev_index = 0;printk("hello_init \n");result = register_chrdev(major, "hello", &hello_ops);if(result < 0){printk("register_chrdev fail \n");return result;}cls = class_create(THIS_MODULE, "hellocls");if (IS_ERR(cls)) {printk(KERN_ERR "class_create() failed for cls\n");result = PTR_ERR(cls);goto out_err_1;}for(dev_index = 0;dev_index < MAX_COM_NUM;dev_index++){pmydev[dev_index].devno = MKDEV(major, minor + dev_index);pmydev[dev_index].class_dev = device_create(cls, NULL, pmydev[dev_index].devno, NULL, "hellodev_%d",minor + dev_index);if (IS_ERR(pmydev[dev_index].class_dev)) {result = PTR_ERR(pmydev[dev_index].class_dev);goto out_err_2;}pmydev[dev_index].data = minor + dev_index + 10; }return 0;
out_err_2:for(dev_index = 0;dev_index < MAX_COM_NUM;dev_index++){if (!IS_ERR(pmydev[dev_index].class_dev)) {device_destroy(cls, pmydev[dev_index].devno);}}class_destroy(cls);
out_err_1:unregister_chrdev(major,"hello");return result;
}
static void hello_exit(void)
{int dev_index;printk("hello_exit \n");for(dev_index = 0;dev_index < MAX_COM_NUM;dev_index++){if (!IS_ERR(pmydev[dev_index].class_dev)) {device_destroy(cls, pmydev[dev_index].devno);}}class_destroy(cls);unregister_chrdev(major,"hello");return;
}
module_init(hello_init);
module_exit(hello_exit);
实验代码如下,实验代码则较为简单,只是打开了两个设备,然后便读取数据,看是否读取到想要的驱动数据。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //closevoid main(void)
{int fd0,fd1;int minor;fd0 = open("/dev/hellodev_5",O_RDWR);if(fd0<0){perror("open fail \n");return;}printf("open /dev/hellodev_5 OK\n");read(fd0,&minor,sizeof(minor));printf("minor of /dev/hellodev_5 =%d\n",minor);close(fd0);fd1 = open("/dev/hellodev_6",O_RDWR);if(fd1<0){perror("open fail \n");return;}printf("open /dev/hellodev_6 OK\n");read(fd1,&minor,sizeof(minor));printf("minor of /dev/hellodev_6 =%d\n",minor);close(fd1);
}
补充内容
用户进程和文件描述符
写一个测试程序,打开一个文本文件。然后延时1000秒。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //close
void main(void)
{int fd;fd = open("./testtxt",O_RDWR|O_CREAT);if(fd < 0){perror("open fail");return;}sleep(1000);close(fd);
}
当上述程序运行时,会被操作系统分配一个pid。在/proc/{pid}/fd
目录下会记录当前进程共打开了哪些文件。
root@ubuntu:# ps -ef | grep testcd/mnt
root 172 80 0 19:54 pts/0 00:00:00 ./test
root@ubuntu:# ll /proc/172/fd
总计 0
dr-x------ 2 root root 0 Sep 19 19:55 ./
dr-xr-xr-x 9 root root 0 Sep 19 19:54 ../
lrwx------ 1 root root 64 Sep 19 19:55 0 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 19 19:55 1 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 19 19:55 2 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 19 19:55 3 -> /home/testtxt*
当一个文件被进程打开时,我们可以通过lsof指令查询当前文件被哪些进程打开。
root@ubuntu:# lsof ./testtxt
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
test 150 root 3u REG 8,32 0 84180 ./testtxt
那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。