10、Linux驱动开发:驱动-进程设备文件内核驱动

news/2024/5/10 13:08:54

目录

🍅点击这里查看所有博文

  随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。

  想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。

  很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。

  同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。

  既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。

  本系列博客所述资料均来自互联网,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

设备文件和硬件设备的关系

  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接口,便可直接通过fileprivate_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;
}

实验代码

  驱动代码如下,需要注意的是,在驱动模块加载时就要完成所支持的从设备上下文的相关变量的赋值,包括但不局限于devnoclass_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

  那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。

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

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

相关文章

新项目,Linux上一键安装MySQL,Redis,Nacos,Minio

大家好&#xff0c;我是 jonssonyan 分享一个我的一个开源项目&#xff0c;这是一个在 Linux 平台上一键安装各种软件的脚本项目&#xff0c;脚本使用 Shell 语言编写&#xff0c;后续还会增加更多软件的一键安装&#xff0c;代码在 GitHub 上全部开源的&#xff0c;开源地址如…

java八股文复习-----2024/03/03

1.接口和抽象类的区别 相似点&#xff1a; &#xff08;1&#xff09;接口和抽象类都不能被实例化 &#xff08;2&#xff09;实现接口或继承抽象类的普通子类都必须实现这些抽象方法 不同点&#xff1a; &#xff08;1&#xff09;抽象类可以包含普通方法和代码块&#x…

使用API有效率地管理Dynadot域名,进行DNS域名解析

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

[HackMyVM] 靶场 Wave

kali:192.168.56.104 主机发现 arp-scan -l # arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:d2:e0:49, IPv4: 192.168.56.104 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.56.1 0a:00:27:00:00:05 (Un…

【MySQL】表的约束——空属性、默认值、列描述、zerofill、主键、自增长、唯一键、外键

文章目录 MySQL表的约束1. 空属性2. 默认值3. 列描述4. zerofill5. 主键6. 自增长7. 唯一键8. 外键 MySQL 表的约束 MySQL中的表的约束是一种规则&#xff0c;用于限制或保护表中数据的完整性和合法性。约束可以确保数据在插入、更新或删除时满足特定的条件&#xff0c;从而维护…

初学Vue总结

0 Vue概述 问题&#xff1a;我们已经学过了htmlCssjavascript,可以开发前端页面了&#xff0c;但会发现&#xff0c;效率太低了。那么&#xff0c;有没有什么工具可以提高我们的开发效率&#xff0c;加快开发速度呢&#xff1f; 他来了&#xff0c;他来了&#xff0c;他大佬似…