一、函数介绍
#include <unistd.h>    //System V
#include <sys/ioctl.h> //BSD and Linux

int ioctl(int d, int request, ...);
  • 附加链接:http://www.baike.com/wiki/ioctl
  • 作用:操作特殊文件的底层设备参数。特别是,字符特殊文件(如终端)的许多操作字符特性可能由ioctl()请求控制
  • ioctl函数时IO操作的杂物箱,通常可以做到其他IO函数不能达到的功能。终端IO是使用ioctl最多的地方
  • 注意事项:为了使用这个调用,需要一个打开的文件描述符。open(2)调用通常有一些不想要的副作用,在Linux下可以通过使用O_NONBLOCK标志来避免这些副作用
  • 参数:

    • 参数1:必须是一个打开的文件描述符
    • 参数2:是一个依赖于设备的请求代码。
    • ...参数:是指向内存的无类型指针。它在本质上是char *argp(在void *有效之前的日子),并将因此而命名
    • 无论参数是in参数还是out参数,以及argu‐ment argp的大小(以字节为单位),ioctl()请求都在其中编码。宏和用于指定ioctl()请求的定义位于文件中
  • 返回值:

    • 成功:返回0
    • 一些ioctl()请求使用返回值作为输出参数,并在成功时返回一个非负的值
    • 错误时:返回-1,并适当地设置errno,值如下:
      • EBADF:d不是一个有效的描述符

      • EFAULT:argp引用一个不可访问的内存区域

      • EINVAL:请求或argp无效

      • ENOTTY:d与字符专用设备无关

      • ENOTTY:指定的请求不适用于描述符d引用的对象类型

APUE编程:18---文件I/O之(ioctl函数)_应用程序

二、头文件的注意事项
  • 在上面的函数原型中,我们只给出了ioctl函数本身所要求的头文件。通常,还要求另外的设备专用头文件。例如,除POSIX.1所说明的基本操作之外,终端IO的ioctl命令都需要头文件<termios.h>
三、ioctl在设备驱动程序中的使用
  • 每个设备驱动程序可以定义它自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令

下图总结了FreeBSD支持的通用的ioctl命令的一些类别:

APUE编程:18---文件I/O之(ioctl函数)_应用程序_02

  • 磁带操作使我们可以在磁带上写一个文件结束标志、倒带、越过指定个数的文件或记录等,这些都是read、write、lseek等很难做到的。所以,对这些设备进行操作最容易的方法就是使用ioctl
四、用ioctl设置终端窗口的大小
  • 终端窗口大小:大多数UNIX系统都提供了一种跟踪当前终端窗口大小的方法,在窗口大小发生变化时,使内核通知前台进程组

winsize结构体:

  • 内核为每个终端和伪终端都维护了一个winsize结构体
struct winsize
{
    unsigned short ws_row;    //rows,in characters
    unsigned short ws_col;    //columns,in characters
    unsigned short ws_xpixel; //horizontal size,pixels(unused)
    unsigned short ws_ypixel; //vertical size,pixels(unused)
};

ioctl对窗口大小的操作

  • 用ioctl的TIOCGWINSZ命令可以获取winsize结构体的当前值
  • 用ioctl的TIOCSWINSZ命令可以将winsize结构体的新值存储到内核中。如果此新值与存储在内核中的当前值不同,则前台进程组会受到SIGWINCH信号(该信号的系统默认动作为忽略)
  • 除了存储winsize结构体的当前值以及在此值改变时产生一个信号之外,内核对winsize结构体不进行任何其他操作。对winsize结构体中的值进行解析完全是应用程序的工作
  • 提供这种操作的功能目的是:当窗口大小发生变化时应用程序能够得到通知(如vi编辑器)。应用程序接收此信号后,可以获取窗口大小的新值,然后重绘屏幕

演示案例:

  • 下面程序打印当前窗口大小,然后休眠。每个窗口大小改变时,程序捕获到SIGGWINCH信号,然后打印新的窗口大小(我们必须用一个信号终止此程序)
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif

//打印窗口大小信息
static void pr_winsize(int fd)
{
    struct winsize size;
    if(ioctl(fd,TIOCGWINSZ,(char *)&size)<0)
    {
    	perror("TIOCGWINSZ error");
    	exit(EXIT_FAILURE);
    }
    printf("%d rows,%d columns\n",size.ws_row,size.ws_col);
}

static void sig_winch(int signo)
{
    printf("SIGWINCH received\n");
    pr_winsize(STDIN_FILENO);
}

int main () 
{
    if(isatty(STDIN_FILENO)==0)
        exit(1);
	
    //捕获信号,当每次窗口大小改变时,打印窗口大小信息
    if(signal(SIGWINCH,sig_winch)==SIG_ERR)
    {
        perror("signal error");
        exit(EXIT_FAILURE);
    }
	
    pr_winsize(STDIN_FILENO);
    
    //循环休眠,等待接受信号
    for(;;)
        pause();
		
    return 0;
}

演示结果

  • 在一个带窗口终端的系统上运行此程序,当每次改变窗口大小时,就会打印信息。可以按中断键终止程序

APUE编程:18---文件I/O之(ioctl函数)_应用程序_03

五、ioctl与struct ifreq、 struct ifconf结构体的使用