嵌入式 浅谈fcntl与ioctl函数

fcntl:

#include <sys/ioctl.h>
ioctl函数提供对连接到fd的设备驱动程序的属性和操作的访问
其原型为
extern int ioctl(int fd,unsigned long int request[,char * arg ....])
fd 是打开设备的描述符  request 是函数代码(类似特定的操作一样,这是与设备相关的)
arg  是参数  可选的
返回值是 失败 -1  并设置 errno
成功一般是0  但是任然要视具体情况而定
因为有些属性需要使用返回值 这时不一定是0了
void prinft_screen_dimension(int fd)
{
  int  fd;  //
  struct winsize buf;

  if (ioctl( fd, TIOCGWINZ,&buf ) != -1){
   printf("%d rows x %d cols\n",buf.ws_row,buf.ws_col);
   printf("%d wide x %d tall\n",buf.ws_xpixel,buf.ws_ypixel);
  }
}
每种设备都有自己所支持的设备属性集以及ioctl操作集
fcntl  是用来设置和修改描述符的的属性
一般来说我们对文件的写入都是带有缓冲的,
但是在数据比较重要的时候担心意外事故导致缓冲数据丢失
这时我们可以设置数据存入是同步的(即关闭缓冲属性)
描述符的属性被编码到一个整数中了
fcntl通过读写该整数位来设置文件描述符的属性。
使用fcntl取得此值  修改后 再用fcntl写回

#include <fcntl.h>
fcntl 原型为 
extern int fcntl(int fd, int _cmd,... )

调用形式有
fcntl(int fd,int cmd);
fcntl(int fd,int cmd,long arg)
fcntl(int fd,int cmd,struct flock *lock)
fd 值相应的描述符,cmd 是指相应的操作 
相应操作可以查看man fcntl  会发现fcntl不只会干这些哟。

这里我们用fcntl进行演示(关闭fd的缓冲属性 注意只有块设备与实际文件有这个属性)
int fd,va;

va = fcntl( fd,F_GETFD);
va | = O_SYNC;
fcntl( fd,F_SETFD,va);

当然也可以直接在open的同时进行设置。
不过open的属性设置没有用fcntl那样灵活。
 
注:其中cmd主要包括:
参数fd代表欲设置的文件描述符。
参数cmd代表打算操作的指令。
有以下几种情况:
F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。新描述符与fd共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符标志被清除。请参考dup2()。
F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。
F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
参数lock指针为flock 结构指针,定义如下
struct flock
{
short int l_type;
short int l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
l_len表示锁定区域的长度,若果为0表示从起点(由l_whence和 l_start决定的开始位置)开始直到最大可能偏移量为止。即不管在后面增加多少数据都在锁的范围内。
返回值 成功则返回0,若有错误则返回-1,错误原因存于errno.
fcntl()用来操作文件描述符的一些特性。fcntl 不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。
fcntl的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列四个命令有特定返回值:F_DUPFD、F_GETFD、F_GETFL、F_GETOWN.第一个返回新的文件描述符,接下来的两个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。
 
ioctl:

#include<unistd.h>

int ioctl( int fd, int  request, .../* void *arg */ );                              返回0——成功, -1——出错

第一个参数 fd 指示某个文件描述符(当然也包括 套接口描述符)

第二个参数 request 指示要ioctl执行的操作

第三个参数 总是某种指针,具体的指向类型依赖于 request 参数

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

      套接口操作

      文件操作

      接口操作

      ARP 高速缓存操作

      路由表操作

      流系统

下表列出了网络相关ioctl 请求的request 参数以及arg 地址必须指向的数据类型:

 
类别       request         说明             (第三个参数)数据类型

        SIOCATMARK   是否位于带外标记               int

套接口   SIOCSPGRP    设置套接口的进程ID或组ID        int

        SIOCGPGRP    获取套接口的进程ID或组ID        int

文件     FIONBIO     设置/清除非阻塞IO标识           int

        FIOASYNC    设置/清除信号驱动异步IO标识      int

        FIONREAD    获取接收缓冲区中的字节数         int

                        ……

       SIOCGIFCONF   获取所有接口的清单         struct ifconf

接口    SIOCSIFADDR   设置接口的ip地址          struct ifreq

       SIOCGIFADDR    获取接口地址             struct ifreq

       SIOCSIFFLAGS   设置接口标识             struct  ifreq

       SIOCGIFFLAGS   获取接口标识             struct ifreq

       SIOCSIFDSTADDR 设置点到点地址           struct ifreq

       SIOCGIFDSTADDR 获取点到点地址           struct  ifreq

       SICSIFBRDADDR  设置广播地址             struct ifreq

       SICGIFBRDADDR  获取广播地址             struct ifreq

                        ……

       SIOCSARP    创建/修改ARP表项           struct arpreq

ARP    SIOCGARP     获取ARP表项              struct arpreq

       SIOCDARP     删除ARP表项              struct arpreq

路由                  ……

有很多request请求没有列出,而且不同的系统所提供的request也有所不同,比如linux就不提供SIOCGSIZIFCONF请求)

上面的表中,用ioctl执行接口操作的请求时,要用到结构体 struct ifconf 与 struct ifreq

struct ifconf{

    int ifc_len;                 // 缓冲区ifcu_buf的大小

    union{

        caddr_t ifcu_buf;            // 其实就是char *类型。

        struct ifreq *ifcu_req;    // 为ifconf分配空间时我们用ifcu_buf指针;当要取得或设置该缓冲区中的ifreq类型时,则用这个指针

    }ifc_ifcu;

};

#define  ifc_buf  ifc_ifcu.ifcu_buf    //buffer address

#define  ifc_req  ifc_ifcu.ifcu_req    //array of structures returned

struct ifreq
{
    char ifrn_name[IFNAMSIZ];  /* if name, e.g. "eth0"  */
    union {
         struct sockaddr ifru_addr;
          struct sockaddr ifru_dstaddr;
         struct sockaddr  ifru_broadaddr;
         struct sockaddr ifru_netmask;
         struct   sockaddr ifru_hwaddr;
          short ifru_flags;
         int ifru_ivalue;
          int ifru_mtu;
         struct  ifmap ifru_map;
          char ifru_slave[IFNAMSIZ]; /* Just fits the size */
          char ifru_newname[IFNAMSIZ];
         void * ifru_data;
          struct if_settings ifru_settings;
    } ifr_ifru;
};

#define ifr_name ifr_ifrn.ifrn_name                           /* interface  name  */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr                    /* MAC  address   */
#define ifr_addr ifr_ifru.ifru_addr                               /* address  */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr                     /* other end of p-p  lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr            /* broadcast  address */
#define ifr_netmask ifr_ifru.ifru_netmask                  /*  interface net  mask */
#define ifr_flags ifr_ifru.ifru_flags                              /*  flags  */
#define ifr_metric ifr_ifru.ifru_ivalue                          /*  metric  */
#define ifr_mtu  ifr_ifru.ifru_mtu                                 /* mtu   */
#define ifr_map  ifr_ifru.ifru_map                                /* device map  */
#define  ifr_slave ifr_ifru.ifru_slave                             /* slave  device  */
#define ifr_data ifr_ifru.ifru_data                               /*  for use by interface */
#define  ifr_ifindex ifr_ifru.ifru_ivalue                          /* interface  index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue                   /* link  bandwidth */
#define  ifr_qlen ifr_ifru.ifru_ivalue                             /* Queue length   */
#define ifr_newname ifr_ifru.ifru_newname              /* New  name  */
#define ifr_settings ifr_ifru.ifru_settings                    /*  Device/proto settings*/

ifconf 结构包含了 ifreq 结构指针,  在使用ifconf之前,我们得先为其ifcu_buf指针(或者说ifcu_req指针)分配缓冲区

其他不知道该怎么说。 

贴代码吧。  一个用ioctl实现的类似ifconfig命令的小程序。


#include <stdio.h>//printf()
#include <unistd.h>//ioctl()
#include <sys/ioctl.h>//ioctl
#include <sys/socket.h>//socket()
#include <net/if.h>//struct ifconf{} & struct ifreq{}
#include <string.h>//strcpy()
#include <arpa/inet.h>//inet_ntoa()
#include <stdlib.h>//malloc() & free()

int print_if_addr(int fd, char *interface_name);//打印接口的ip地址
int print_if_mac(int fd, char *interface_name);//打印接口的mac地址
int print_if_broadaddr(int fd, char *interface_name);//打印接口的广播地址
int print_if_mask(int fd, char *interface_name);//打印接口的掩码
int print_if_mtu(int fd, char *interface_name);//打印接口的mtu
int print_all_interface();//打印所有接口的基本信息
int print_if_addr6(char *interface_name);//打印接口的ipv6地址
int print_interface_info(char *interface_name);//打印接口的以上所有信息
int set_if_up(char *interface_name);//启动接口
int set_if_down(char *interface_name);//关闭接口
int set_if_ip(char *interface_name, char *ip_str);//设置接口的ip地址
void usage();//打印该程序的使用手册

int main(int argc, char **argv)
{
int sockfd;

printf("\n**********funway:用ioctl函数来实现ifconfig命令的效果**********\n");

switch(argc)
{
case 1:
print_all_interface();
break;
case 2:
print_interface_info(argv[1]);
break;
case 3:
if(strcmp(argv[2], "up") == 0)
set_if_up(argv[1]);
else if(strcmp(argv[2], "down") == 0)
set_if_down(argv[1]);
else
set_if_ip(argv[1], argv[2]);
break;
default:
usage();
break;
}

return 0;
}

void usage()
{
printf("usage: ./myifconfig [interface [down|up|ip]]\n");
}

int print_if_addr(int fd, char *if_name)
{
struct sockaddr_in *ip;
struct ifreq ifr;

strcpy(ifr.ifr_name, if_name);

if(ioctl(fd, SIOCGIFADDR, &ifr) < 0)
{
perror("ioctl SIOCGIFADDR error");
return -1;
}
ip = (struct sockaddr_in *)&ifr.ifr_addr;//获得ipv4地址
printf("IP: %s\n", inet_ntoa(ip->sin_addr));//将ipv4地址转换为主机字节序的字符串并输出
return 0;
}

int print_if_broadaddr(int fd, char *if_name)
{
struct sockaddr_in *ip;
struct ifreq ifr;

strcpy(ifr.ifr_name, if_name);

if(ioctl(fd, SIOCGIFBRDADDR, &ifr) < 0)
{
perror("ioctl SIOCGIFBRDADDR error");
return -1;
}
ip = (struct sockaddr_in *)&ifr.ifr_broadaddr;//获得广播地址
printf("Broadcast: %s\n", inet_ntoa(ip->sin_addr));
return 0;
}

int print_if_mask(int fd, char *if_name)
{
struct sockaddr_in *ip;
struct ifreq ifr;

strcpy(ifr.ifr_name, if_name);

if(ioctl(fd, SIOCGIFNETMASK, &ifr) < 0)
{
perror("ioctl SIOCGIFNETMASK error");
return -1;
}
ip = (struct sockaddr_in *)&ifr.ifr_ifru.ifru_netmask;//获得子网掩码。注意!我们仍放在struct aockaddr_in结构中返回
printf("Mask: %s\n", inet_ntoa(ip->sin_addr));
return 0;
}

int print_if_mac(int fd, char *if_name)
{
unsigned char *p;//注意! 这里要用unsigned char,而不是char!因为char要对[1xxx xxxx]这样的数进行补码运算的。
//但我们等下要打印的mac地址是不需要符号的数值
struct ifreq ifr;

strcpy(ifr.ifr_name, if_name);

if(ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
{
perror("ioctl SIOCGIFHWADDR error");
return -1;
}
p = (char *)&ifr.ifr_ifru.ifru_hwaddr.sa_data[0];//获得接口的MAC地址,用字符串指针返回
printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", *p, *(p+1), *(p+2), *(p+3), *(p+4), *(p+5));
//printf(" MAC:%02x:%02x:%02x:%02x:%02x:%02x\n", *p++, *p++, *p++, *p++, *p++, *p++);
//这么写会导致输出为倒序。这并不是p指针有什么问题,不信你可以用
// for(;;)
//printf(p++);
//来试验就是正确的,我猜倒序的原因是编译器的优化问题吧
return 0;
}

int print_if_mtu(int fd, char *if_name)
{
unsigned int mtu;
struct ifreq ifr;

strcpy(ifr.ifr_name, if_name);

if(ioctl(fd, SIOCGIFMTU, &ifr) < 0)
{
perror("ioctl SIOCGIFMTU error");
return -1;
}
mtu = ifr.ifr_ifru.ifru_mtu;//获得子网掩码。注意!我们仍放在struct aockaddr_in结构中返回
printf("MTU: %d\n", mtu);
return 0;
}

int print_if_addr6(char *if_name)
{
unsigned int mtu;
struct ifreq ifr;
int sockfd;
if((sockfd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
{
perror("Socket error");
return -1;
}// 创建用来检查网络接口的套接字

/*strcpy(ifr.ifr_name, if_name);

if(ioctl(fd, SIOCGIFMTU, &ifr) < 0)
{
perror("ioctl SIOCGIFMTU error");
return -1;
}
mtu = ifr.ifr_ifru.ifru_mtu;//获得子网掩码。注意!我们仍放在struct aockaddr_in结构中返回
printf("ipv6: %d\n", mtu);
*/
//未写完,不知道怎么获得ipv6地址。。。
return 0;
}

int print_all_interface()
{
struct ifconf ifc;
struct ifreq *ifr_p;
int sockfd, len, old_len = 0, i;
char *buf;

if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("Socket error");
return -1;
}// 创建用来检查网络接口的套接字
len = 10 * sizeof(struct ifreq);
for( ; ; )
{
if((buf = malloc(len)) == NULL)
{
perror("malloc error");
return -1;
}
ifc.ifc_len = len;
ifc.ifc_buf = buf;
if(ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
{
perror("ioctl SIOCGIFCONF error");
return -1;
}
if(ifc.ifc_len == old_len)
break;
old_len = ifc.ifc_len;
len += 10 * sizeof(struct ifreq);
free(buf);
}
printf("we have %d interfaces\n", ifc.ifc_len / sizeof(struct ifreq));
for(i = 0; i < ifc.ifc_len / sizeof(struct ifreq); i++)
{
ifr_p = &ifc.ifc_req[i];
printf("\ninterface [%s]:\n", ifr_p->ifr_name);

print_if_addr(sockfd, ifr_p->ifr_name);
print_if_broadaddr(sockfd, ifr_p->ifr_name);
print_if_mask(sockfd, ifr_p->ifr_name);
print_if_mac(sockfd, ifr_p->ifr_name);
print_if_mtu(sockfd, ifr_p->ifr_name);
}
close(sockfd);
return 0;
}

int print_interface_info(char *if_name)
{
int sockfd;
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("Socket error");
return -1;
}// 创建用来检查网络接口的套接字

printf("%s:\n", if_name);
print_if_addr(sockfd, if_name);
print_if_broadaddr(sockfd, if_name);
print_if_mask(sockfd, if_name);
print_if_mac(sockfd, if_name);
print_if_mtu(sockfd, if_name);
close(sockfd);
return 0;
}

int set_if_up(char *if_name)//启动接口
{
struct ifreq ifr;
int sockfd;

if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("Socket error");
return -1;
}// 创建用来检查网络接口的套接字

strcpy(ifr.ifr_name, if_name);
if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0)
{
perror("ioctl SIOCGIFFLAGS error");
return -1;
}
ifr.ifr_flags |= IFF_UP;
if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0)
{
perror("ioctl SIOCSIFFLAGS error");
return -1;
}
return 0;
}

int set_if_down(char *if_name)//关闭接口
{
struct ifreq ifr;
int sockfd;

if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("Socket error");
return -1;
}// 创建用来检查网络接口的套接字

strcpy(ifr.ifr_name, if_name);
if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0)
{
perror("ioctl SIOCGIFFLAGS error");
return -1;
}
ifr.ifr_flags &= ~IFF_UP;//将IIF_UP取反后与原来的标志进行 与运算。
if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0)
{
perror("ioctl SIOCSIFFLAGS error");
return -1;
}
return 0;
}

int set_if_ip(char *if_name, char *ip_str)//设置接口的ip地址
{
struct ifreq ifr;
struct sockaddr_in ip_addr;
int sockfd;

if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("Socket error");
return -1;
}// 创建用来检查网络接口的套接字
ip_addr.sin_family = AF_INET;
if(inet_pton(AF_INET, ip_str, &ip_addr.sin_addr) < 1)
{
perror("error ipv4 addr:");
return -1;
}
strcpy(ifr.ifr_name, if_name);
memcpy(&ifr.ifr_addr, &ip_addr, sizeof(struct sockaddr_in));
if(ioctl(sockfd, SIOCSIFADDR, &ifr) < 0)
{
perror("ioctl SIOCSIFADDR error");
return -1;
}
return 0;
}
 
 
ioctl在驱动中:
设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口。我们前面的“Hello World”驱动程序已经可以提供读写功能了,在这里我们将扩展我们的驱动以支持设备控制接口,在Linux中这个接口是通过ioctl函数来实现的。

设备控制接口(ioctl 函数
回想一下我们在
字符设备驱动 中介绍的struct file_operations 结构,这里我们将介绍一个新的方法:

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);


这是驱动程序设备控制接口函数(ioctl函数)的内核原型定义,struct inode *struct file* 描述了操作的文件,unsigned int 描述了ioctl命令号,这是一个重要的参数,我们稍后会对它做详细介绍。最后一个参数是unsigned long数据类型,描述了ioctl命令可能带有的参数,它可能是一个整数或指针数据。
  • ioctl命令号
ioctl命令号是这个函数中最重要的参数,它描述的ioctl要处理的命令。Linux中使用一个32位的数据来编码ioctl命令,它包含四个部分:dir:type:nr:size。
  • dir
代表数据传输的方向,占2位,可以是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,当然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。
  • type
描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'。
  • nr
ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号。
  • size
ioctl命令的参数大小,一般14位。ioctl命令号的这个数据成员不是强制使用的,你可以不使用它,但是我们建议你指定这个数据成员,通过它我们可以检查用户空间数据的大小以避免错误的数据操作,也可以实现兼容旧版本的ioctl命令。
我们可以自己来直接指定一个ioctl命令号,它可能仅仅是一个整数集,但Linux中的ioctl命令号都是有特定含义的,因此通常我们不推荐这么做。其实Linux内核已经提供了相应的宏来自动生成ioctl命令号:

_IO(type,nr)
_IOR(type,nr,size)
_IOW(type,nr,size)
_IOWR(type,nr,size)


宏_IO用于无数据传输,宏_IOR用于从设备读数据,宏 _IOW用于向设备写数据,宏_IOWR用于同时有读写数据的IOCTL命令。相对的,Linux内核也提供了相应的宏来从ioctl命令号种解码相应的域值:

_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)


这些宏都定义在<asm/ioctl.h>头文件中(一般在<asm-generic.h>头文件中)。一般在使用中,先指定各个IOCTL命令的顺序编号(一般从0开始),然后根据使用的环境用这些宏来自动生成IOCTL命令号,在后面的例子中你可以了解实际的使用场景。
  • ioctl返回值
ioctl函数的返回值是一个整数类型的值,如果命令执行成功,ioctl返回零,如果出现错误,ioctl函数应该返回一个负值。这个负值会作为errno值反馈给调用此ioctl的用户空间程序。关于返回值的具体含义,请参考<linux/errno.h>和<asm/errno.h>头文件。
  • ioctl参数
这里有必要说明一下ioctl命令的参数,因为它很容易犯错误。如果ioctl命令参数仅仅是一个整数,那么事情很简单了,我们可以在ioctl函数中直接使用它。但如果它是一个指针数据,那么使用上就要小心了。首先要说明这个参数是有用户空间的程序传递过来的,因此这个指针指向的地址是用户空间地址,在Linux中,用户空间地址是一个虚拟地址,在内核空间是无法直接使用它的。为了解决在内核空间使用用户空间地址的数据,Linux内核提供了以下函数,它们用于在内核空间访问用户空间的数据,定义在<asm/uaccess.h>头文件中:

unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n);
unsigned long __must_check copy_from_user(void *to,
const void __user *from, unsigned long n);


copy_from_user和copy_to_user一般用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:

#define get_user(x,ptr)
#define put_user(x,ptr)


其中,x是内核空间的简单数据类型地址,ptr是用户空间地址指针。
我们需要牢记:在内核中是无法直接访问用户空间地址数据的。因此凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。
这里有必要再一次强调的是,在内核模块或驱动程序的编写中,我们强烈建议你使用内核提供的接口来生成并操作ioctl命令号,这样可以对命令号赋予特定的含义,使我们的程序更加的健壮;另一方面也可以提高程序的可移植性。

举例
好了,是时候举个例子了。我们将扩展我们的helloworld驱动添加ioctl函数。

首先,我们添加一个头文件来定义ioctl接口需要用到的数据(hello.h):

#ifndef _HELLO_H
#define _HELLO_H
#include <asm/ioctl.h>
#define MAXBUF 20
typedef struct _buf_data{
int size;
char data [MAXBUF];
}buf_data;

#define HELLO_IOCTL_NR_BASE            0
#define HELLO_IOCTL_NR_SET_DATA     (HELLO_IOCTL_NR_BASE + 1)
#define HELLO_IOCTL_NR_MAX             (HELLO_IOCTL_NR_GET_BUFF + 1)

#define HELLO_IOCTL_SET_DATA           _IOR('h', HELLO_IOCTL_NR_SET_DATA, buf_data*)

#endif


然后为我们的驱动程序添加ioctl接口hello_ioctl,并实现这个函数:

static int hello_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int cmd_nr;
int err;
buf_data buff;

err = 0;
cmd_nr = _IOC_NR (cmd);
switch (cmd_nr){
case HELLO_IOCTL_NR_SET_DATA:
if (copy_from_user(&buff, (unsigned char *)arg, sizeof(buf_data)))
{
err = -ENOMEM;
goto error;
}
memset(hello_buf, 0, sizeof(hello_buf));
memcpy(hello_buf, buff.data, buff.size);
break;
default:
printk("hello_ioctl: Unknown ioctl command (%d)\n", cmd);
break;
}

error:
return err;
}

static struct file_operations hello_fops = {
.read = hello_read,
.write = hello_write,
.open = hello_open,
.ioctl = hello_ioctl,
.release = hello_release,
};



后记
到这里我们已经向您展示了Linux内核驱动程序的设备控制接口(ioctl接口),详细的介绍了它的使用,并给出了一个实际的例子,尽管它很简单,但已经足够了。到这里你可以写出一个标准的Linux驱动程序了。不过这里还有个问题,那就是我们不得不从/proc/devices文件里读取设备号然后手动创建设备节点。我们是否可以让系统自动的创建这个设备节点文件呢?当然可以。不过在那之前,我们必须深入了解Linux的设备驱动模型。后面的章节我们就详细的介绍Linux的设备驱动模型及Hotplug机制。

你可能感兴趣的:(嵌入式 浅谈fcntl与ioctl函数)