六、Linux系统IO编程—fcntl 函数 和 ioctl 函数详解

一、fcntl()函数

1. 函数的原型
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock * lock);
  • 函数说明:fcntl()用来操作文件描述词的一些特性

  • 函数功能:可以改变已经打开文件的性质

  • 参数说明:

    • fd:代表欲设置的文件描述符
    • cmd:代表欲操作的指令。针对cmd的值,fcntl能够接受第三个参数int arg。有以下几种情况:
      • F_DUPFD:用来查找大于或等于参数 arg 的最小且仍未使用的文件描述符,并且复制参数 fd 的文件描述符。执行成功则返回新复制的文件描述符。请参考dup2()。复制文件描述符,新的文件描述符作为函数返回值返回。
      • F_GETFD:获取文件描述符,通过第三个参数设置
      • F_SETFD:设置文件描述符,通过第三个参数设置
      • F_GETFL/F_SETFL:
        • 取得/设置文件状态标志,通过第三个参数设置
        • 可以更改的几个标志是:O_APPEND、O_NONBLOCK、SYNC、O_ASYNC(O_RDONLY、O_WRONLY和O_RDWR不适用)
      • F_GETLK 取得文件锁定的状态。
      • F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
      • F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
  • 参数lock指针为flock 结构指针,定义在下面。

  • 返回值:

    • 成功则返回0,若有错误则返回-1,错误原因存于errno。
  • 常见的功能:

    • 复制一个现存的描述符,新文件描述符作为函数返回值(cmd = F_DUPFD)
    • 获得/设置文件描述符标志(cmd = F_GETFD 或 F_SETFD)
    • 获得/设置文件状态标志(cmd = F_GETFL 或 F_SETFL)
    • 获得/设置文件锁(cmd = F_SETLK、cmd= F_GETLK、F_SETLKW)
  • 文件记录锁是fcntl函数的主要功能。

    • 记录锁:实现只锁文件的某个部分,并且可以灵活的选择是阻塞方式还是立刻返回方式
  • 当fcntl用于管理文件记录锁的操作时,第三个参数指向一个struct flock *lock的结构体

struct flock
{
    short_l_type;    /*锁的类型*/
    short_l_whence;  /*偏移量的起始位置:SEEK_SET,SEEK_CUR,SEEK_END*/
    off_t_l_start;   /*加锁的起始偏移*/
    off_t_l_len;    /*上锁字节*/
    pid_t_l_pid;   /*锁的属主进程ID */
}; 
  • l_type 有三种状态:
    • F_RDLCK 建立一个供读取用的锁定
    • F_WRLCK 建立一个供写入用的锁定
    • F_UNLCK 删除之前建立的锁定
  • l_whence 也有三种方式:
    • SEEK_SET 以文件开头为锁定的起始位置。
    • SEEK_CUR 以目前文件读写位置为锁定的起始位置
    • SEEK_END 以文件结尾为锁定的起始位置。

注意事项:

  • short_l_type用来指定设置共享锁(F_RDLCK,读锁)还是互斥锁(F_WDLCK,写锁)。
    当short_l_type的值为F_UNLCK时,传入函数中将解锁。
    每个进程可以在该字节区域上设置不同的读锁。
    但给定的字节上只能设置一把写锁,并且写锁存在就不能再设其他任何锁,且该写锁只能被一个进程单独使用。
    这是多个进程的情况。
    单个进程时,文件的一个区域上只能有一把锁,若该区域已经存在一个锁,再在该区域设置锁时,新锁会覆盖掉旧的锁,无论是写锁还时读锁。

  • l_whence,l_start,l_len三个变量来确定给文件上锁的区域。
    l_whence确定文件内部的位置指针从哪开始,l_star确定从l_whence开始的位置的偏移量,两个变量一起确定了文件内的位置指针先所指的位置,即开始上锁的位置,然后l_len的字节数就确定了上锁的区域。
    特殊的,当l_len的值为0时,则表示锁的区域从起点开始直至最大的可能位置,就是从l_whence和l_start两个变量确定的开始位置开始上锁,将开始以后的所有区域都上锁。
    为了锁整个文件,我们会把l_whence,l_start,l_len都设为0。

2. 案例

文件状态标志设置
io.h

#ifndef _IO_H
#define _IO_h

//定义外部复制函数
extern void copy(int fdin,int fdout);
//设置文件状态标志
extern void setfl(int fd,int flag);
//清除文件状态标志
extern void clearfl(int fd,int flag);
#endif

io.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "io.h"

//定义缓冲区的大小
#define BUFFER_SIZE 1024

//文件的读写拷贝函数
void copy(int fdin,int fdout)
{
	char buff[BUFFER_SIZE];//定义缓冲区存放数据
	ssize_t size;
	//read函数返回值为读取的字节数,返回0表示无数据可读
	while((size=read(fdin,buff,BUFFER_SIZE))>0)
	{
		if(write(fdout,buff,size)!=size)
		{	
			fprintf(stderr,"write data fail:%s\n",strerror(errno));
		}
		exit(1);
	}	
	if(size<0)//read函数返回值为读取的字节数,如果小于0就相当于读错误了
	{
		
		fprintf(stderr,"read data fail:%s\n",strerror(errno));
		exit(1);//相当于return 1
	}

}

//设置文件状态标志
void setfl(int fd,int flag)
{	
	 int val;
	 //获取原来的文件标志
	 val =fcntl(fd,F_GETFL);
	 if(val<0)
	 {
		perror("fcnlt set error"); 
         }
	 //增加文件状态标志
	 val = val|flag;
	 //设置新的文件状态标志
	 if(fcntl(fd,F_SETFL,val)<0)
	{
		perror("fcnlt set error");
	}
}
//清除文件状态标志
void clearfl(int fd,int flag)
{
	 int val;
	 //获取原来的文件标志
	 val =fcntl(fd,F_GETFL);
	 if(val<0)
	 {
		perror("fcntl clear error"); 
	 }
	 //增加文件状态标志
	 val &= ~flag;
	 //设置新的文件状态标志
	 if(fcntl(fd,F_SETFL,val)<0)
	{
		perror("fcntl clear error");
	}	
}	

file_append.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "io.h"

int main(int argc,char* argv[])
{	
	int fd;
	int ret;
	size_t size;

	//判断输入的参数是否满足条件,即输入参数为输入2个文件
	if(argc!=3)
	{
		fprintf(stderr,"Usage: %s content  destfile\n", argv[0]);
		exit(1);
	}
	
	fd=open(argv[2],O_WRONLY);
	//设置文件状态标志
	setfl(fd,O_APPEND);
	//清除文件标志状态
	//clearfl(fd,O_APPEND);
	
 	sleep(10);
	
	size=sizeof(char)*strlen(argv[1]);
	if(write(fd,argv[1],size)!=size)
	{
		perror("write error");
		close(fd);
		exit(1);
	}
	
		
	return 0;
}

使用设置文件状态标志,将文件标志的状态更改成为O_APPEND。也可清除相关的文件状态标志,就会出现被覆盖的现象。更改为O_APPEND的运行结果如下:
在这里插入图片描述

二、ioctl()函数

1. ioctl()函数

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。

#include <unistd.h>
#include <sys/ioctl.h>
int ioctl(int fd, int cmd,)
  • 函数说明:
    • ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数 控制设备的I/O通道。
    • 此函数可以说是 I/O操作的杂物箱。不能用前面说的函数表示的 I/O 操作通常都能用 ioctl 表示。
    • 终端 I/O 是 ioctl 的最大使用方面,主要用于设备 I/O 控制
  • 函数功能:
    • 控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段。
    • 用于向设备发控制和配置命令 ,有些命令需要控制参数,这些数据是不能用read / write 读写的,称为Out-of-band数据。
    • 也就是说,read / write 读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。
  • 参数说明:
    • fd :就是用户程序打开设备时使用open函数返回的文件标示符,
    • cmd :就是用户程序对设备的控制命令,
    • … : 省略号,那是一些补充参数,即可变参数列表,一般最多一个,有或没有是和cmd的意义相关的。
  • 返回值:成功为0,出错为-1
2. ioctl的必要性

如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码(cmd)告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

3. 案例

从键盘 IO 设备中,读取键盘的数据。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/input.h>
  
int main(int argc,char *argv[])
{
	int fd=-1;
	char name[256]="UnKnow";
	struct input_event event;//事件源
	int ret=0;

	//打开设备	
	if((fd=open("/dev/input/event1",O_RDONLY))<0)
	{
		perror("open error");
		exit(1);
	}
	
	//使用EVIOCGNAME宏的作用获得设备的名称
	if((ioctl(fd,EVIOCGNAME(sizeof(name)),name)<0))	
	{
		perror("evdev ioctl error");
		exit(1);
	}
	printf("this devices name is %s\n",name);
	
	while(1)
	{
		//读写打开的设备
		ret=read(fd,&event,sizeof(event));
		if(ret<0)
		{	
			printf("read event error");
			exit(1);
		}	
	
		if(EV_KEY==event.type)
		{
			//如果事件是一个按键码
			printf("key code is %d\n",event.code);
			if(event.code==16)
			{
				break;
			}

		}
	}
	close(fd);
	return 0;
}

运行结果如下:
在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值