为什么大多数设备驱动程序中的每个函数都是静态的?因为静态函数在文件范围之外是不可见的。那么,这些驱动函数是如何被用户空间应用程序调用的呢?
3 回答
请记住,在 C 中,一切都是地址。这意味着如果你有地址,你可以调用一个函数。内核有一个名为的宏EXPORT_SYMBOL
就是这样做的。它导出函数的地址,以便可以调用驱动程序函数而不必放置头声明,因为这些函数有时在编译时是不知道的。在这种情况下,静态限定符只是为了确保它们仅通过此方法调用,而不是从可能包含该驱动程序代码的其他文件中调用(在某些情况下,包含驱动程序代码头并直接调用它们不是一个好主意) .
编辑:因为有人指出我没有涵盖用户空间。
驱动程序函数通常不直接通过用户空间调用(除了 SYSCALL 指令的 x86 实现,它有时会做一些小技巧来保存上下文切换)。所以这里的 static 关键字没有区别。它只会在内核空间上有所不同。正如@Cong Wang 所指出的,函数通常被放入函数指针的结构中,以便可以通过简单地让结构指向该结构(例如file_ops、调度程序、文件系统、网络代码等)来调用它们。
因为这些静态函数不应该直接在模块之外使用。它们由模块中的其他函数调用,其中可以是 ioctl 的接口或任何回调。这就是为什么它们可以从用户空间调用,它们只是在调用路径中。
看一下网络虚拟模块:
dummy_dev_init() 显然是静态的:
static int dummy_dev_init(struct net_device *dev)
{
dev->dstats = alloc_percpu(struct pcpu_dstats);
if (!dev->dstats)
return -ENOMEM;
return 0;
}
但它是注册此网络设备时调用的 ->ndo_init() 的回调。
static const struct net_device_ops dummy_netdev_ops = {
.ndo_init = dummy_dev_init,
.ndo_uninit = dummy_dev_uninit,
.ndo_start_xmit = dummy_xmit,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_rx_mode = set_multicast_list,
.ndo_set_mac_address = eth_mac_addr,
.ndo_get_stats64 = dummy_get_stats64,
.ndo_change_carrier = dummy_change_carrier,
};
显然没有人应该直接调用 dummy_dev_init() 。
内核有数千个模块,它们是(或曾经是)所有目标文件,通过类似于链接的过程动态加载 - 或者实际上是链接 - 到可执行文件中。你能想象如果他们都导出所有函数名会有多少名称冲突,除非static
指定,否则默认 C 行为是这样吗?
用户空间应用程序不能直接调用驱动程序函数,但还有其他交互方式。