或许我们最熟悉就是文件的操作,文件有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),
};
|
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部分的实现.