上一节中已经学习了文件描述符的复制,复制方法有三种,其中最后一种fcntl还并未使用到,关于这个函数,不光只有复制文件描述符的功能,还有其它一些用法,本节就对其进行一一剖析:
![](https://i-blog.csdnimg.cn/blog_migrate/9870846684c934430301c4a1b9b56aae.png)
fcntl常用操作:
![](https://i-blog.csdnimg.cn/blog_migrate/8679ad4e052f1a00fbab7b2ebfc47582.png)
这里,我们将上节当中用dup或dup2实现复制文件描述符改用fcntl,程序如下:
![](https://i-blog.csdnimg.cn/blog_migrate/cc7775d12cbc514089bc367c922d72d7.png)
先将test2.txt的内容清空,以便进行测试,编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/e0e2bc9809e451bc14d69e69da745ed1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1484c3ccc7371a492f814db859bce235.png)
通过man来查看下它的说明:
![](https://i-blog.csdnimg.cn/blog_migrate/aec0434cf6956385c28c00db7a42acc8.png)
【说明:关于这一的操作命令,等之后学到进程时再来学习,先这边记录一下】
![](https://i-blog.csdnimg.cn/blog_migrate/d14e0da323f4585097ea5b129add76e1.png)
上一节也有介绍过,先回顾一下都有哪些状态标志:
![](https://i-blog.csdnimg.cn/blog_migrate/9a534f74f039664bf15c5523cbddebe5.png)
也就是说,通过这个命令,能更改文件状态标志,说来有些难理解,下面以实例代码来进行一一说明:
![](https://i-blog.csdnimg.cn/blog_migrate/639201413579e696b71499e8f69e84ae.png)
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/903be89b8c7c5d4563f137b51bd5f9de.png)
当输入内容时,read才读取完,并打印出输入的内容:
![](https://i-blog.csdnimg.cn/blog_migrate/264fbd408ee620933bb96773ae011f16.png)
这时本来的文件状态,但是,可以fcntl函数,来改变这种阻塞状态为非阻塞状态,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/296f6a700f2e1fbc9397bfad0d230e1a.png)
具体代码:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
char buf[1024] = {0};
int ret;
int flags;
flags = fcntl(0, F_GETFL, 0);//通过F_GETFL来获得标准输入的状态
if (flags == -1)
ERR_EXIT("fcntl get flag error");
ret = fcntl(0, F_SETFL, flags | O_NONBLOCK);//通过F_SETFL来改变文件的状态为非阻塞0_NONBLOCK,但是为了保留其它状态,所以设置之前需获得状态,再进行与操作
if (ret == -1)
ERR_EXIT("fcntl set flag error");
ret = read(0, buf, 1024);
if (ret == -1)
ERR_EXIT("read error");
printf("buf=%s\n", buf);
return 0;
}
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/de0479bfc29a80ce3d22e9ebdfd58ac7.png)
实际上,对于上面这个错误,对应的错误代码是:
![](https://i-blog.csdnimg.cn/blog_migrate/4082320615982cb18b03bc1226bf4a71.png)
【注意:在设置状态时,一定得先用F_GETFL获取状态,然后再去用F_SETFL去设置,因为我们只想设置非阻塞的状态,对于其它状态如:写状态,读状态等想保留】
关于上面这段设置状态的代码,可以进行封装,以便进行复用,如下:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void set_flag(int fd, int flags);//设置文件状态标志函数声明
int main(int argc, char *argv[])
{
char buf[1024] = {0};
int ret;
set_flag(0, O_NONBLOCK);//这时,经过代码封装之后,代码就显得比较干净了
ret = read(0, buf, 1024);
if (ret == -1)
ERR_EXIT("read error");
printf("buf=%s\n", buf);
return 0;
}
//设置文件状态标志
void set_flag(int fd, int flags)
{
int val;
val = fcntl(fd, F_GETFL, 0);
if (val == -1)
ERR_EXIT("fcntl get flag error");
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0)
ERR_EXIT("fcntl set flag error");
}
![](https://i-blog.csdnimg.cn/blog_migrate/69842dc03979d9d86b451a3b849e884c.png)
另外,我们还可以封装一个清除文件状态标志的函数,跟设置很类似,如下:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void set_flag(int fd, int flags);
void clr_flag(int fd, int flags);//清除文件状态标志函数声明
int main(int argc, char *argv[])
{
char buf[1024] = {0};
int ret;
set_flag(0, O_NONBLOCK);
clr_flag(0, O_NONBLOCK);//清除了非阻塞的状态标记,也就是等于最终还是阻塞状态
ret = read(0, buf, 1024);
if (ret == -1)
ERR_EXIT("read error");
printf("buf=%s\n", buf);
return 0;
}
void set_flag(int fd, int flags)
{
int val;
val = fcntl(fd, F_GETFL, 0);
if (val == -1)
ERR_EXIT("fcntl get flag error");
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0)
ERR_EXIT("fcntl set flag error");
}
//清除文件状态标志
void clr_flag(int fd, int flags)
{
int val;
val = fcntl(fd, F_GETFL, 0);
if (val == -1)
ERR_EXIT("fcntl get flag error");
val &= ~flags;
if (fcntl(fd, F_SETFL, val) < 0)
ERR_EXIT("fcntl set flag error");
}
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/29dfd8e85a7a18c7ec20602a51a37c72.png)
【提示:设置状态中的:val |= flags;和清除状态中的:val &= ~flags;是怎么一回事,可以看一下C程序中的位操作,这个也可以参考:http://www.cnblogs.com/webor2006/p/3440026.html】
![](https://i-blog.csdnimg.cn/blog_migrate/1ed434acf79e7be76ab2d53ae8fc28cb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5d653e75553c7bcaa593362192f440d2.png)
先来解释一下结构体字段:
![](https://i-blog.csdnimg.cn/blog_migrate/6438c0bb4785c17963815df22a8182bd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3093cde72f3802cfea2e36f91d1e87a2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9a26a84d442193841524844fb5bdd1aa.png)
说了这么多,可能还是有点不是很好理解,下面以实际代码来进行说明:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
int fd;
fd = open("test2.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1)
ERR_EXIT("open error");
struct flock lock;
memset(&lock, 0, sizeof(lock));
lock.l_type = F_WRLCK;//加上排它锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLK, &lock) == 0)
{
printf("lock success\n");
printf("press any key to unlock\n");
getchar();
lock.l_type = F_UNLCK;//释放锁
if (fcntl(fd, F_SETLK, &lock) == 0)
printf("unlock success\n");
else
ERR_EXIT("unlock fail");
}
else
ERR_EXIT("lock fail");
return 0;
}
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/259e227ab334b3a84bf404cfdd901b42.png)
![](https://i-blog.csdnimg.cn/blog_migrate/52ffad7f421667ff4c3cac4f1edd999d.png)
实际上,上面出错的错误代码也是EAGAIN,查看一下fcntl函数:
![](https://i-blog.csdnimg.cn/blog_migrate/6aa346a0690ab2520b8ac08aafe8e854.png)
【注意:如果要给文件加读锁,则文件需要有读的权限;如果要给文件写写锁,则文件也需要有写的权限】
另外,设置文件锁,还有另外一种操作命令:F_SETLKW,它跟F_SETLK有啥区别呢?且看下代码:
![](https://i-blog.csdnimg.cn/blog_migrate/3e1ab5f2b6f2b236705d4e06ae3a27af.png)
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/d0672bf6c6d7adce64254d42067ba197.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b9d8e786940d493d068d4f90e92f4cd8.png)
【总结:F_SETLK设置锁时,如果进程没有成功设置上锁,则会立马给出错误提示;F_SETLKW设置锁时,如果进程没有成功设置上锁,会阻塞,类似于线程的同步一样,当对方的锁释放时,则才可以对文件进行上锁】
另外,如果想获得阻塞进程的ID,可以用F_GETLK,它会将id保存在flock结构体中的l_pid当中,关于这个,就不做实验了,比较简单,好了,关于linux系统编程中的文件/IO,就告一段落了,下回会进入linux系统编程的新的东东,下回见!