Linux IO

一、lseek()

 #include <unistd.h>
 #include <sys/types.h>
 off_t lseek(int filedes, off_t offset, int whence);
 //whence取值可选0(SEEK_SET),1(SEEK_CUR),2(SEEK_END);
 //返回值:新的偏移量(成功),-1(失败)

 如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。

 

二、dup(), dup2(),fcntl()

#include <unistd.h>

int dup(int oldfd);

dup用来复制参数oldfd所指的文件描述符。当复制成功是,返回最小的尚未被使用过的文件描述符,若有错误则返回-1.错误代码存入errno中。返回的新文件描述符和参数oldfd指向同一个文件,这两个描述符共享同一个数据结构,共享所有的锁定,读写指针和各项全现或标志位。dup(oldfd)等价于fcntl(oldfd, F_DUPFD, 0)

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
    int fd=open("text.txt", O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR);
    if(fd < 0)
    {
        printf("Open Error!\n");
        return 0;
    }
    int fd2=dup(fd);
    if(fd2<0)
    {
        printf("Error!\n");
        return 0;
    }
    char buf[1000];
    int n;
    while((n=read(STDIN_FILENO, buf,1000)) > 0)  //接受键盘输入,并将其存入buf所指向的缓存中   
    {
        if(write(fd2, buf, n)<n)    //将buf所指向的缓存区的n个字节的数据写入到由文件描述符fd2所指示文件中
        {
            printf("Write Error!!\n");
            return 0;
        }
    }
    return 0;
}

运行结果:

#include <unistd.h>

int dup2(int oldfd,int newfd);

返回:若成功则为非负的描述符,若出错则为-1

dup2函数复制描述符表项oldfd到描述符表项newfd,覆盖描述符表项  newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd;

dup2(4,1)前和后,文件b的引用计数增加了

 

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char c1, c2, c3;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    fd2 = Open(fname, O_RDONLY, 0);
    fd3 = Open(fname, O_RDONLY, 0);
    dup2(fd2, fd3);

    Read(fd1, &c1, 1);
    Read(fd2, &c2, 1);
    Read(fd3, &c3, 1);
    printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

运行结果:

在Open()获得3个文件描述符之后,创建了3个打开文件表,但都指向同一个v-node表。dup2(fd2,fd3)之后fd3描述符表项指向的打开文件表表项变成fd2所指向的打开文件表表项,然后他们共享文件位置,对不同描述符的读操作可以从文件的不同位置获取数据。

 

三、close()

#include<unistd.h>

int close(int fd);//成功返回0,出错返回-1

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
    	int fd=open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 0666);
	printf("success=%d\n",fd);
	close(fd);
	printf("closed\n");
    	return 0;
}

结果:

这个因为我命令行没有带文件名,所以open出错返回-1,看到fd=-1;

这个在当前目录下新建了a文件,因为0,1,2,分别表示标准输入,标准输出,标准错误,所以fd返回的是3.

可以看到新建的a的权限并不是666而是644,这是因为屏蔽位为022

如果在源程序里面加入umask(0000)就能得到0666的新文件访问权限

 

四、重定位

//把文件描述符3重定向到标准输出屏幕
exec 3>&1  
//把标准输出重定向到test文件,接下来两行echo输出都会在test文件输出
exec 1>test
echo "这句话被存到test文件中"
echo "还有这句"
//然后再次把文件描述符1定位到屏幕
exec 1>&3
echo "这句话输出到显示器"

自定义的文件描述符因为不像描述符1,所有的输出都会自然找它,然后看它是定向到显示器还是某个文件。所以当我们想找描述符6的时候我们要用&来引用它。其实我们可以把文件描述符想像成一个文件的引用,它可以指向任何一个文件(包括显示器),指向的过程就是我们修改默认位置的过程。而用&符号来找到它指向的目标文件,从而向其写入数据。

五、read 和父子进程共享文件


int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
    char c1, c2;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    Read(fd1, &c1, 1);
    if (fork()) {
	/* Parent */
	sleep(s);
	Read(fd1, &c2, 1);
	printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
    } else {
	/* Child */
	sleep(1-s);
	Read(fd1, &c2, 1);
	printf("Child: c1 = %c, c2 = %c\n", c1, c2);
    }
    return 0;
}

头文件:#include <unistd.h>

定义函数:unsigned int sleep(unsigned int seconds);

函数说明:sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断.

返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
之前没看到int s = getpid() & 0x1;然后在想如果sleep(1-s)是负数怎么办,然后看到sleep的参数是unsigned的会强转

这个打印出来了pid号

原因:在fork之后,子进程有父进程描述符表的副本,父子进程共享相同的打开文件表集合,因此共享相同的文件位置。

六、write()

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char *fname = argv[1];
    fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
    Write(fd1, "pqrs", 4);	

    fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
    Write(fd3, "jklmn", 5);
    fd2 = dup(fd1);  /* Allocates new descriptor */
    Write(fd2, "wxyz", 4);
    Write(fd3, "ef", 2);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

/*abcde.txt
*/

结果:

abcde文件之前内容是:abcde

之后为:

先Open了abcde文件,因为flags参数加上了O_TRUNK,意思是如果文件存在就把文件清空,并把指针指向的当前文件位置改为文件开头。( S_IRUSR|S_IWUSR设置了该文件的权限是当前用户可读可写。)
然后再abcde文件当中写上了4个字符,文件位置是4
pqrs
然后fd3是以APPEND方式打开文件:每次在写操作之前,设置文件位置到文件的结尾处,所以Write(fd3, "jklmn", 5)在文件后加上jklmn,
pqrsjklmn
fd2和fd1指向相同的打开文件表表项,在文件位置4之后写上wxyz,覆盖了之前的jklm
pqrswxyzn
fd3设置文件位置到文件的结尾处,在后面加上ef
pqrswxyznef

七、C语言输出缓冲区

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("h");
    printf("e");
    printf("l");
    printf("l");
    printf("o");
    printf("\n");//1
    fflush(stdout);//2
    exit(0);
}

结果:

第一次什么都没干

第二次把2注释掉了

第三次把1注释掉了

第四次把1、2都注释掉了

unix上标准输入输出都是带有缓存的,一般是行缓存。

对于标准输出,需要输出的数据并不是直接输出到终端上,而是首先缓存到某个地方,当遇到行刷新标志或者该缓存已满的情况下,才会把缓存的数据显示到终端设备上。

ANSI C中定义换行符'\n'可以认为是行刷新标志。所以,printf函数没有带'\n'是不会自动刷新输出流,直至缓存被填满。

但fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上 。

在进程退出时也会清空标准输出缓冲区

八、stat()

stat函数检索文件的信息(文件的元数据)

#include<unistd.h>

#includ<sys/stat.h>

int stat(const char* filename,struct stat *buf);

int fstat(int fd,struct stat *buf);

返回:若成功则为0,出错-1

/* $begin statcheck */
#include "csapp.h"

int main (int argc, char **argv) 
{
    struct stat stat;
    char *type, *readok;

    /* $end statcheck */
    if (argc != 2) {
	fprintf(stderr, "usage: %s <filename>\n", argv[0]);
	exit(0);
    }
    /* $begin statcheck */
    Stat(argv[1], &stat);
    if (S_ISREG(stat.st_mode))     /* Determine file type */
	type = "regular";
    else if (S_ISDIR(stat.st_mode))
	type = "directory";
    else 
	type = "other";
    if ((stat.st_mode & S_IRUSR)) /* Check read access */
	readok = "yes";
    else
	readok = "no";

    printf("type: %s, read: %s\n", type, readok);
    exit(0);
}

结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值