第3章 文件I/O(4)_dup、dup2、fcntl和ioctl函数

时间:2021-05-11 21:58:16

5. 其它I/O系统调用

(1)dup和dup2函数

头文件

#include<unistd.h> 

函数

int dup(int oldfd);

int dup2(int oldfd, int newfd);

返回值

若成功返回新文件描述符,出错返回-1

功能

文件描述符的复制(将oldfd复制给newfd

参数

old:原先的文件描述符

newfd: 新文件描述符

备注

(1)由dup返回的新文件描述符一定是当前可用文件描述符中最小数值

(2)用dup2则可以用newfd参数指定新描述符的数值。如果newfd己经打开,则先将其关闭。如果old等于newfd,则dup2返回newfd,而不关闭它。

(3)在进程间通信时可用来改变进程的标准输入和标准输出设备。(4)注意,复制的只是3个内核结构中的文件描述符中的文件表项指针也就是newfd与oldfd指向了同一个文件表项。但要注意并不复制文件表项本身和inode节点项这两个内核数据结构。

【编程实验】1. 自定义的cat命令

//io.h

#ifndef __IO_H__
#define __IO_H__

extern void copy(int fdin, int fdout);//文件复制
extern void set_fl(int fd, int flag); //设置文件状态标志
extern void clr_fl(int fd, int flag); //取消文件状态标志


#endif

//io.c

#include "io.h"
#include
<unistd.h>
#include
<errno.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<fcntl.h>

//编译命令:gcc -o obj/io.o -Iinclude -c src/io.c

#define BUFFER_LEN 1024 //与分区文件块大小一致。可以通过
//tune2fs -l /dev/sda1命令查看


void copy(int fdin, int fdout)
{
char buffer[BUFFER_LEN];

ssize_t size;

//保证从文件开始处复制
lseek(fdin, 0L, SEEK_SET);
lseek(fdout,
0L, SEEK_SET);

while((size = read(fdin, buffer, BUFFER_LEN)) > 0){
if(write(fdout, buffer, size) != size)
{
fprintf(stderr,
"write error: %s \n", strerror(errno));
exit(
1);
}
}

if (size < 0 )
{
fprintf(stderr,
"read error: %s\n",strerror(errno));
exit(
1); //return 1;
}
}

void set_fl(int fd, int flag) //设置文件状态标志
{
//获取原来的文件状态标志
int val = fcntl(fd, F_GETFL);

//增加新的文件状态标志
val |= flag;

//重新设置文件状态标志
if(fcntl(fd, F_SETFL, val) < 0)
{
perror(
"fcntl error");
}
}

void clr_fl(int fd, int flag) //取消文件状态标志
{

//获取原来的文件状态标志
int val = fcntl(fd, F_GETFL, val);

//清除指定的文件状态标志(置0)
val &= ~flag;

//重新设置文件状态标志
if(fcntl(fd, F_SETFL, val) < 0 )
{
perror(
"fcntl error");
}
}

//cat.c

#include "io.h"
#include
<unistd.h>
#include
<fcntl.h>
#include
<stdio.h>

//模拟cat命令,设编译后的程序名也叫cat
//实验1:bin/cat file1.txt file2.txt
//实验2:bin/cat < file1.txt
//实验3:bin/cat > file1.txt

int main(int argc, char* argv[])
{
int fd_in = STDIN_FILENO; //0
int fd_out = STDOUT_FILENO; //1

int i = 0;
for(i=1; i<argc; i++){
//1.输入cat file1.txt file2.txt时会将file1和file2
// 分别拷贝到标准输出,即输出到屏幕
fd_in = open(argv[1], O_RDONLY);
if(fd_in < 0)
{
perror(
"open error");
continue;
}

copy(fd_in, fd_out);
close(fd_in);
}
//2.如果cat命令后面不带参数,则直接接受键盘输入。
//3.如果输入cat < file1.txt,由于<表示输入重定向,Linux
//会将file1.txt这个参数作为输入,复制给STDIN_FILENO。而不会将其
//传给argv参数。即这种情况下,argc==1,表示程序本身,argv中不包含file1.txt。
if(argc == 1) copy(fd_in, fd_out);

return 0;
}

【编程实验】2.dup和dup2实现重定向

//io.h和io.c与上例相同

#include "io.h"
#include
<unistd.h>
#include
<fcntl.h>
#include
<string.h>
#include
<stdlib.h>
/*
* bin/mcat + file1.txt (+为输入重定向)
* bin/mcat - file1.txt (-为输出重定向)
*/

int main(int argc, char* argv[])
{
int fd_in;
int fd_out;

int i=0;

for(i=1; i<argc; i++){
if(!strcmp("+", argv[i])){
fd_in
= open(argv[++i], O_RDONLY);
if(fd_in < 0){
perror(
"open error");
exit(
1);
}

//将标准输入重定向到文件(即,将fd_in的文件表项指针复制给STDIN_FILENO
//因此,STDIN_FILENO文件描述符与fd_in就同时指向同一个文件表项,也就
//同时指向了file1.txt这个文件。
if(dup2(fd_in, STDIN_FILENO) != STDIN_FILENO){
perror(
"dup2 error");
exit(
1);
}

close(fd_in);
}
else if(!strcmp("-", argv[i])){

fd_out
= open(argv[++i], O_WRONLY | O_CREAT | O_TRUNC, 0777);
if(fd_out < 0){
perror(
"open error");
exit(
1);
}

//将标准输出重定向到文件(即,将fd_out的文件表项指针复制给STDOUT_FILENO
//因此,STDOUT_FILENO文件描述符与fd_out就同时指向同一个文件表项,也就
//同时指向了file1.txt这个文件。
if(dup2(fd_out, STDOUT_FILENO) != STDOUT_FILENO){
perror(
"dup2 error");
exit(
1);
}

close(fd_out);
}
else{
fd_in
= open(argv[i], O_RDONLY);
if(fd_in < 0){
perror(
"open error");
exit(
1);
}

if(dup2(fd_in, STDIN_FILENO) != STDIN_FILENO)
{
perror(
"dup2 error");
exit(
1);
}

close(fd_in);
}

copy(STDIN_FILENO, STDOUT_FILENO);
}

//命令后面不带参数的情况
if(argc ==1)
copy(STDIN_FILENO, STDOUT_FILENO);

return 0;
}

(2)fcntl函数

头文件

#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);

返回值

若成功则依赖于cmd,出错为-1。

功能

可以改变己经打开文件的性质常见的功能:

(1)复制一个现存的描述符,新文件描述符作为函数返回值(cmd=F_DUP)

(2)获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD)

(3)获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)

(4)获取/设置文件锁(cmd=F_SETLK、F_GETLK或F_SETKLW),此时第3个参数为struct flock结构体。

参数

cmd的常见取值:

(1)F_DUPFD:复制文件描述符,新的文件描述符作为函数返回值返回。

(2)F_GETFD/F_SETFD:获取/设置文件描述符,通过第3个参数设置。

(3)F_GETFL/F_SETFL:获取/设置文件状态标志,通过第3个参数设置。可以更改的标志有:O_APPEND、O_NONBLOCK、O_SYNC、O_ASYNC但要注意,O_RDONLY、O_WRONLY和O_RDWR不适用。

【编程实验】设置文件状态标志

//io.h和io.c文件与上例相同

//file_append_flag.c

#include "io.h"
#include
<unistd.h>
#include
<stdio.h>
#include
<stdlib.h> //exit
#include <string.h> //strlen
#include <fcntl.h> //O_WRONLY

int main(int argc, char* argv[]){
if( argc < 3){
printf(
"usage: %s content destfile\n", argv[0]);
exit(
1);
}

int fd = open(argv[2], O_WRONLY); //注意,这里没设置为追加

if(fd < 0){
perror(
"open error");
exit(
1);
}

//设置追加文件标志
set_fl(fd, O_APPEND);

sleep(
10); //为了把定位与写入过程隔开,以便演示多进程同时写入同一文件
//时会出现后启动进程格覆盖之前进程写过的内容。

//往文件尾部追加内容
size_t size = strlen(argv[1])*sizeof(char);
if(write(fd, argv[1], size)!=size){
perror(
"write error");
exit(
1);
}

close(fd);

return 0;
}

(3)ioctl函数

头文件

#include<unistd.h>

#include <sys/ioctl.h>

函数

int ioctl(int fd, int request,…);

返回值

成功为0,出错为-1

功能

控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段

备注

I/O操作的杂物箱。不能用本章中其他函数表示的I/O操作通常都能用ioctl表示。终端I/O是ioctl的最大使用方面,主要用于设置的I/O控制。

【编程实验】读取键盘内容

#include <unistd.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<fcntl.h>
#include
<sys/ioctl.h> //for ioctl
#include <linux/input.h> //keyboard

//说明本程序须在虚拟机,而不能在远程终端上运行!

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/event2", O_RDONLY)) < 0){
perror(
"open error");
exit(
1);
}

//通用EVIOCGNAME命令获取设备名字
if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0){
perror(
"evdev ioctl error\n");
exit(
1);
}

printf(
"The device's name is %s\n", name);

while(1)
{
ret
= read(fd, &event, sizeof(event));
if(ret < 0){
printf(
"read event error\n");
}

if(EV_KEY == event.type) //EV_KEY表示按键类事件,类似的有EV_PWR(电源)、EV_SND(声音)事件
{
//if the event is a key code
printf(" key code is %d \n", event.code);

//quit,press 'q' to quit this application.
if(event.code == 16){
break;
}
}
}

return 0;
}