用户空间与内核的交互---IOCTL

本文深入分析了Linux内核中ioctl函数的使用和架构,以网络设备gre0为例,详细阐述了其在驱动程序和网络编程中的应用。通过解析内核源码,展示了ioctl函数在设备初始化、设备操作以及网络接口配置中的作用,同时介绍了ioctl在用户空间的调用方式及驱动中自定义ioctl的实现方法。最后总结了ioctl函数在Linux系统中的分类与用途。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在procfs一节中我们提到过ioctl,它的作用编写过驱动和从事过网络编程的人,一定不会陌生. 就是由于它架构的思路的精妙之处,屏蔽了大量抽象的东西.这里我们就分析下它的使用和架构,当然这里不会分析ioctl系统调用的实现.这里参考资料有《linux设备驱动程序》,《深入理解linux网络技术内幕》 ,当然也少不了网上好的文章和帖子.

       或许我们最熟悉就是文件的操作,文件有read、write当然还有其他一些的操作,这里的工作就给了ioctl. 这里我们首先说一下驱动程序里关于ioctl函数的使用. 

       这里举一个网络设备的例子: 比如我们创建一个gre接口,在内核里(内核版本3.1.1)net/ipv4/ip_gre.c中 我们看它的内核启动必须初始化的部分:

        module_init(ipgre_init); 

       我们展开 ipgre_init函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
 *  And now the modules code and kernel interface.
 */
 
static int __init ipgre_init(void)
{
    int err;
 
    printk(KERN_INFO "GRE over IPv4 tunneling driver\\n");
 
    err = register_pernet_device(&ipgre_net_ops);
    if (err < 0)
        return err;
 
    err = gre_add_protocol(&ipgre_protocol, GREPROTO_CISCO);
    if (err < 0) {
        printk(KERN_INFO "ipgre init: can't add protocol\\n");
        goto add_proto_failed;
    }
 
    err = rtnl_link_register(&ipgre_link_ops);
    if (err < 0)
        goto rtnl_link_failed;
 
    err = rtnl_link_register(&ipgre_tap_ops);
    if (err < 0)
        goto tap_ops_failed;
 
out:
    return err;
 
tap_ops_failed:
    rtnl_link_unregister(&ipgre_link_ops);
rtnl_link_failed:
    gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO);
add_proto_failed:
    unregister_pernet_device(&ipgre_net_ops);
    goto out;
}



这里我们只关心 register_pernet_device(&ipgre_net_ops); 故名思意它会注册一个网络设备

我们看看ipgre_net_ops的初始化


1
2
3
4
5
6
static struct pernet_operations ipgre_net_ops = {
    .init = ipgre_init_net,
    .exit = ipgre_exit_net,
    .id   = &ipgre_net_id,
    .size = sizeof(struct ipgre_net),
};
这里我们看内核必须初始化的部分 .init = ipgre_init_net :



1
2
3
4
5
6
7
8
9
10
11
12
static int __net_init ipgre_init_net(struct net *net)
{
    struct ipgre_net *ign = net_generic(net, ipgre_net_id);
    int err;
 
    ign->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "gre0",
                       ipgre_tunnel_setup);
    if (!ign->fb_tunnel_dev) {
        err = -ENOMEM;
        goto err_alloc_dev;
    }
......


 这里创建gre0接口,并用 ipgre_tunnel_setup来完成部分初始化工作.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void ipgre_tunnel_setup(struct net_device *dev)
{
    dev->netdev_ops      = &ipgre_netdev_ops;
    dev->destructor  = ipgre_dev_free;
 
    dev->type        = ARPHRD_IPGRE;
    dev->needed_headroom     = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
    dev->mtu     = ETH_DATA_LEN - sizeof(struct iphdr) - 4;
    dev->flags       = IFF_NOARP;
    dev->iflink      = 0;
    dev->addr_len        = 4;
    dev->features        |= NETIF_F_NETNS_LOCAL;
    dev->priv_flags      &= ~IFF_XMIT_DST_RELEASE;
}


这里ipgre_netdev_ops 即完成了设备相关的操作函数的初始化(如果用户空间调用就会调用相应的函数)


1
2
3
4
5
6
7
8
9
10
11
12
static const struct net_device_ops ipgre_netdev_ops = {
    .ndo_init       = ipgre_tunnel_init,
    .ndo_uninit     = ipgre_tunnel_uninit,
#ifdef CONFIG_NET_IPGRE_BROADCAST
    .ndo_open       = ipgre_open,
    .ndo_stop       = ipgre_close,
#endif
    .ndo_start_xmit     = ipgre_tunnel_xmit,
    .ndo_do_ioctl       = ipgre_tunnel_ioctl,
    .ndo_change_mtu     = ipgre_tunnel_change_mtu,
    .ndo_get_stats      = ipgre_get_stats,
};


 我们看到了关于设备ioctl的初始化.对没错. 这里内核已经很好的把底层具体的ioctl操作和gre0设备关联了起来.

 下面就看看用户空间如何调用:

 这里简单的说明下:

 /* test */

 int fd;

 struct ifreq ifr;

 fd= open("/dev/gre0");

 if(fd < 0 ) return -1;

 ...

 if( ioctl(fd,SIOCGETTUNNEL,&ifr))

 ..... 

 当然用户空间也要有SIOCGETTUNNEL的定义 ,内核空间的是在include/linux/if_tunnel.h中定义.这里所说是驱动里私有的ioctl的使用.

 #define SIOCGETTUNNEL (SIOCDEVPRIVATE + 0) 

#define SIOCADDTUNNEL (SIOCDEVPRIVATE + 1)

 #define SIOCDELTUNNEL (SIOCDEVPRIVATE + 2)

......

#define SIOCCHG6RD (SIOCDEVPRIVATE + 11)

 在linux系统默认给dev预留了16个私有的ioctl,在include/linux/sockios.h中


1
2
3
4
5
6
7
8
9
10
11
12
/* Device private ioctl calls */
 
/*
 *  These 16 ioctls are available to devices via the do_ioctl() device
 *  vector. Each device should include this file and redefine these names
 *  as their own. Because these are device dependent it is a good idea
 *  _NOT_ to issue them to random objects and hope.
 *
 *  THESE IOCTLS ARE _DEPRECATED_ AND WILL DISAPPEAR IN 2.5.X -DaveM
 */
  
#define SIOCDEVPRIVATE  0x89F0  /* to 89FF */


 但是在实际情况中这个16个并不够用,或者会产生混乱或者冲突,所以在驱动中还有另外一种定义和实现方法,也更为常用: 即通用驱动ioctl的实现方法:

     IO(type,nr),_IOR(type,nr,datatype),_IOW(type,nr,datatype)。它们在包含的中有定义,所以在我们的驱动中应包含这个头文件。其中_IO()用做无参数的命令编号,_IOR()用做从驱动中读取数据的命令编号,_IOW()用做写入数据命令。 LDD中用:define SCULL_IOC_MAGIC 'k' ,define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,1,int)表示SCULL_IOCSQUANTUM命令编号为向驱动中写数据,命令编号为1,传送参数类型为int.

     下面就看一下简单的使用方法:

 #define MY_MAGIC 'k' 

 #define MY_GDEVNAME _IOR(MY_MAGIC, 1,int)

 当然这个定义需要内核和用户空间都有定义. 这里不再详述,具体请阅读ldd3 第六章高级字符驱动程序操作一章.

 这里分析当然会有不到之处,仅作为好的学习的开始.

 当然上面主要说明了驱动中ioctl的使用,也就不得不说下网络编程中ioctl的使用,对于网络编程中自定义这里不再说明.

 这里仅就已经有的很多命令调用做一个简单的总结. 

 具体的定义在include/linux/sockios.h中

 ioctl 函数

 本函数影响由fd 参数引用的一个打开的文件。

 #include int ioctl( int fd, int request, ... ); 

返回0 :成功

 -1 :出错

 第三个参数总是一个指针,但指针的类型依赖于request 参数。 

我们可以把和网络相关的请求划分为6 类:

 套接口操作

 文件操作

 接口操作 

ARP 

高速缓存操作 

路由表操作

 流系统

 具体的使用和分类请参考《unix网络编程》.下面贴出网络ioctl调用流程图: 

 

这样ioctl就统一了起来,驱动的ioctl和网络的ioctl.其实实现思路都是一样的,见开始gre0部分的实现.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值