设计Linux char设备驱动程序,以便多个进程可以读取

时间:2015-01-23 00:05:11

标签: linux linux-device-driver

我注意到对于串行设备,例如/dev/ttyUSB0,多个进程可以打开设备,但只有一个进程获取字节(以先读取它们为准)。

但是,对于Linux输入API,例如/dev/input/event0,多个进程可以打开设备,并且所有进程都能够读取输入事件。

我目前的目标:

我想为多个多位置开关编写驱动程序(例如,具有3个或4个可能位置的滑动开关),其中应用程序可以获得任何开关位置变化的通知。理想情况下,我想使用Linux输入API,但似乎Linux输入API不支持多位置开关的概念。所以我正在考虑制作一个与Linux输入API具有类似功能的自定义驱动程序。

两个问题:

  • 从驱动程序设计的角度来看,为什么Linux输入API和Linux串行设备之间的行为存在差异?我认为,对于多个进程来说,所有人都可以打开一个串口并且都可以监听传入的字节。
  • 编写Linux字符设备驱动程序的好方法是什么,以便它像Linux输入API一样,所以多个进程可以打开设备并读取所有数据?

4 个答案:

答案 0 :(得分:9)

区别在于部分历史,部分原因是期望模型不同。

  • event子系统设计用于将来自多个编写者的简单事件单向通知到系统中,只需很少(或没有)配置选项。

  • tty子系统旨在用于潜在大量数据的双向端到端通信,并提供相当灵活(尽管是相当巴洛克式)的配置机制。

从历史上看,tty子系统是与系统通信的主要机制:您可以插入电话"电传打字机"进入一个串口,位进出。来自不同供应商的不同电传类型使用不同的协议,因此termios接口诞生了。为了使系统在多用户上下文中运行良好,在内核中添加了缓冲(并且可以进行配置)。 tty子系统的期望模型是中等智能端点之间的点对点链接,它们将就它们之间传递的数据达成一致意见。

虽然有些情况下,单个作家,多个读者和#34;在tty子系统(连接到串口的GPS接收器,例如不断报告其位置)中有意义,这不是系统的主要目的。但是你可以很容易地完成这个"多个读者"在用户空间。

另一方面,event系统基本上是一种用于鼠标和键盘之类的中断机制。与远程类型不同,输入设备是单向的,对它们产生的数据几乎不提供控制。缓冲数据也没什么意义。没人会对十分钟前鼠标移动的位置感兴趣。

我希望能回答你的第一个问题。

对于你的第二个问题:"它取决于"。你想要完成什么?什么是"长寿"的数据?您还必须问自己,将复杂性放在内核中是否有意义,或者将它放在用户空间中是不是更好。

向多个读者提供数据并不是特别困难。您可以为每个阅读器创建一个接收缓冲区,并在数据进入时填充每个接收缓冲区。如果数据的速度快于读者可以使用它,那么事情会变得更有趣,但即便如此,这也是解决的问题。看一下网络堆栈的灵感!

如果您的设备很简单并且只是产生事件,那么您可能只想成为输入驱动程序?

如果不了解您想要完成的任务,您的第二个问题就更难回答。

添加特定目标后更新:

当我进行位置切换时,我通常只创建一个角色设备并实现pollread。如果你想要花哨并有很多开关,你可以做mmap但我不会打扰。

用户空间只需打开/dev/foo并读取当前状态并开始轮询。当您的开关改变状态时,您只需唤醒读者,他们就会再次阅读。所有的读者都会醒来,他们都会读到新的状态,每个人都会很开心。

小心,只有在您的交换机安定后才能唤醒读卡器'许多位置开关非常嘈杂,它们会在很大程度上反弹。

换句话说:我会完全忽略输入系统。正如你猜测的那样,位置开关并不是真正的输入"。

答案 1 :(得分:4)

字符设备如何处理这些语义完全取决于驱动程序的定义和实现。

例如,当然可以为串行设备实现一个驱动程序,该驱动程序将所有读取数据传递给打开了字符驱动程序的每个进程。并且还可以实现输入设备驱动程序,该事件仅向一个进程传递事件,无论哪个进程排队等待接收最新事件。所有这些都是对适当实施进行编码的问题。

不同之处在于,这一切都归结为一个简单的问题:"什么是有意义的"。对于串行设备,已经确定通过单个进程处理任何读取数据更有意义。对于输入设备,已经确定将所有输入事件传递给具有输入设备打开的每个进程更有意义。可以合理地预期,例如,一个进程可能只关心特定的输入事件,例如按下指针按钮#3,而另一个进程想要处理所有指针运动事件。因此,在这种情况下,将所有输入事件分发给所有相关方可能更有意义。

为了简单起见,我忽略了一些方面问题,例如在串行数据传递到所有读取过程的情况下,当其中一个停止从设备读取时应该发生什么。在决定如何实现特定设备的语义时,这也是可以考虑的因素。

答案 2 :(得分:1)

串行输入/事件

您可以尝试查看串行鼠标驱动程序源代码。

这似乎就是您要搜索的内容:来自TTYSx构建input/event设备。

更简单:创建 服务器 ,而不是 驱动程序

从历史上看,我记得的第一个角色设备是 /dev/lp0

能够从多个来源写入,没有重叠或其他冲突, LPR 服务器被破坏了。

要共享设备,您必须

  1. 以独占(rw)模式打开此设备。
  2. 创建一个套接字(un * x或TCP)来监听
  3. 将套接字客户端的请求重定向到设备,可能
  4. 存储设备状态(来自阅读设备的答案)
  5. 在需要时将设备状态发送到套接字的客户端。

答案 3 :(得分:1)

  

编写Linux字符设备驱动程序的好方法是什么,以便它像Linux输入API一样,因此多个进程可以打开设备并读取所有数据?

有关char设备的信息,请参阅.open的{​​{1}}成员。每当用户空间打开设备时,都会调用struct file_operations函数。它可以将打开的文件添加到设备的打开文件列表中(然后.open将其删除)。

char设备数据结构应该最有可能使用内核.release来保存打开文件的列表:

struct list_head

每个文件的数据:

struct my_dev_data {
    ...
    struct cdev         cdev;
    struct list_head    file_open_list;
    ...
}

struct file_data { struct my_dev_data *dev_data; struct list_head file_open_list; ... } 函数中,将打开的文件添加到.open(根据需要使用互斥锁保护这些操作):

dev_data->file_open_list

static int my_dev_input_open(struct inode * inode, struct file * filp) { struct my_dev_data *dev_data; dev_data = container_of(inode->i_cdev, struct my_dev_data, cdev); ... /* Allocate memory for file data and channel data */ file_data = devm_kzalloc(&dev_data->pdev->dev, sizeof(struct file_data), GFP_KERNEL); ... /* Add open file data to list */ INIT_LIST_HEAD(&file_data->file_open_list); list_add(&file_data->file_open_list, &dev_data->file_open_list); ... file_data->dev_data = dev_data; filp->private_data = file_data; } 函数应从.release中删除打开的文件,并释放dev_data->file_open_list的内存。

现在file_data.open函数维护打开文件列表,所有打开的文件都可以读取数据。两种可能的策略:

  1. 每个打开文件的单独读取缓冲区。收到数据后,会将其复制到所有打开文件的缓冲区中。
  2. 设备的单个读缓冲区,但每个打开文件的单独读取指针。一旦通过所有打开的文件读取数据,就可以从缓冲区中释放数据。