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;
}