一、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;
}
运行结果如下: