Linux应用程序开(day14) ──系统调用与标准IO、文件描述符、命名管道

内容:

  • 系统调用与标准IO
  • 文件描述符

系统调用:

open函数

int open(const char *pathname, int flags);int open(const char *pathname,int flags, mode_t mode);

作用:打开文件
参数:
    pathname:文件路径
    flags:以什么方式打开
    mode:若需要创建文件,则文件的权限是什么
返回值:失败返回-1,成功返回文件描述符
flags取值:

取值含义
O_RDONLY以只读的方式打开
O_WRONLY以只写的方式打开
O_RDWR以可读、可写的方式打开
O_CREAT文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC如果文件存在,且为只读或只写的方式打开,则清空文件内容
O_APPEND写文件时,数据添加到文件末尾
O_NONBLOCK当打开的文件是FIFO、字符文件、块文件时,此选项为阻塞标志位

示例:

fd1 = open("test0402",O_CREAT|O_WRONLY,0777);
fd_src = open(argv[1],O_RDONLY);

close函数

int close(int fd);

作用:关闭文件
参数:文件描述符
返回值:成功返回0,失败返回-1

示例:

close(fd_src);

write函数

ssize_t write(int fd, const void *addr,size_t count);

作用:把数据写到文件
参数:
    fd:文件描述符
    addr:要写入的数据的内存首地址
    conte:写入的字节个数
返回值:成功返回实际写入的字符个数,失败返回-1

示例:

write(fd_dest,buf,ret);

read函数

ssize_t read(int fd, void *addr, size_t count);

作用:把数据从文件读出
参数:
    fd:文件描述符
    addr:准备存放数据的内存首地址
    conte:读入的字节个数
返回值:成功返回实际读入的字符个数,失败返回-1

示例:

ret = read(fd_src,buf,sizeof(buf));

标准IO:

fopen函数

FILE* fopen(const char *pathname,const char *mode);

作用:打开文件
参数:
    pathname:文件路径
    mode:流的打开方式
返回值:成功返回指向改流的指针,失败返回NULL
mode取值:

模式功能
r或rb以只读方式打开一个文本文件(不创建文件)
w或wb以写方式打开文件(使文件长度截断为0字节,创建一个文件)
a或ab以添加方式打开文件,即在末尾添加内容,当文件不存在时,创建文件用于写
r+或rb+以可读、可写的方式打开文件(不创建新文件)
w+或wb+以可读、可写的方式打开文件(使文件长度为0字节,创建一个文件)
a+或ab+以添加方式打开文件,打开文件并在末尾更改文件(如果文件不存在,则创建文件)

示例:

	fp_src = fopen(filename,"w");

fclose函数

int fclose(FILE *stream);

作用:关闭文件
参数:文件描述符
返回值:成功返回0,失败返回EOF

示例:

	fclose(fp_src);

fwrite函数

size_t fwrite(const void *ptr, size_t size,size_t nobj, FILE *stream);

作用:把数据写到文件
参数:
    ptr:要写入的数据
    size:要写入的数据的大小
    nobj:写入的数据块的个数
    stream:要操作的数据流
返回值:成功返回实际写入的字符个数

示例:

		fwrite("    ",4,1,fp_src);

fread函数

size_t fread(void *ptr, size_t size,size_t nobj, FILE *stream);

作用:把数据从文件读出
参数:
    ptr:要存放数据的首地址
    size:要读入的数据的大小
    nobj:读出的数据块的个数
    stream:要操作的数据流
返回值:成功返回实际读出的字符个数

示例:

ret = fread(arg,10,1,fp_src);

文件描述符:

描述

    在Linux中万事万物皆是文件,而想要访问文件,就需要用到文件描述符。每个进程都有一张文件描述符的表,其中0 1 2 分别是标准输入、标准输出、标准错误。在进程中打开其他文件的时候,系统会返回文件描述符表中最小可用的文件描述符表,然后进行记录。这里有两个复制文件描述符的函数,分别是dup()和dup2(),下面我们进行介绍;

dup函数

int dup(int oldfd);

作用:拷贝一个文件描述符
参数:旧的文件描述符
返回值:成功返回新的文件描述符,失败返回-1
注意:新的文件描述符和旧的文件描述符指向同一个文件,共享旧文件描述符所有的锁定、读写位置、权限。
示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

void main(){

	int fd1,fd2;
	char buf[] = "helloworld";
	char buf1[] = "hi everyone";

	fd2 = dup(1);
	printf("fd2 is %d\n",fd2);
	write(1,buf,sizeof(buf));
	write(fd2,buf,sizeof(buf1));
	
	fd1 = open("test0402",O_CREAT|O_RDWR,0777);
	printf("fd1 is %d\n",fd1);
	if(fd1<0)
	{
		perror("fd1 error \n");
		exit(-1);
	}

	close(1);
	dup(fd1);
	printf("glq20200402\n");

	close(1);
	dup(fd2);
	printf("abcdefgh\n");
}

我们对其文件描述符的变化过程进行分析(下图中的“=”指变化过程,例如“3=1”指3复制成了1):
在这里插入图片描述

dup2函数

int dup2(int oldfd, int newfd);

作用:拷贝一个文件描述符
参数:旧的文件描述符,新的文件描述符
返回值:成功返回新的文件描述符,失败返回-1
注意:如果新的文件描述符已经被使用,则会先关闭,再复制。
实例代码(文件描述符代码在注释):

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	int fd1;
	int fd2 = 3;
	int err = 0;
								//默认打开 0 1 2
	err = dup2(1,fd2); //将3 复制成1 同时返回3 || 0 1 2 3=1
	if(err<0)
	{
		perror("dup2");
	}
	printf("fd2=%d,err=%d\n", fd2, err);//fd2=3,err=3
	fd1 = open("test", O_CREAT|O_RDWR, S_IRWXU);//生成了一个新的4 || 0 1 2 3->1 4(fd1)
	dup2(fd1,1);//1复制成4(fd1) 0 1->4 2 3=1 4(fd1)
	printf("hello world\n");//1->4(test文件) 所以写到文件内
	dup2(fd2,1);//1->fd2(3)->1 || 0 1->4->1 2 3->1 4(fd1)
	printf("I love you \n");//此时1是标准输出,所以输出到屏幕
	return 0;

命名管道(FIFO):

描述:

  命名管道和管道(pipe)类似,都是用来实现不同进程之间通信的,但是FIFO是以文件形式存放在文件系统的,可以通过ls命令实实在在的查看到。FIFO文件存放在文件系用中,但是内容存放在内存中。pipe只能是有子父关系的进程才可以通信,但是FIFO不是,任何进程之间都可以进行通信。FIFO是有名字的,不同的进程通过管道的名字进行通信,FIFO的缓冲区大小为4KB。它和pipe一样,传送的数据是无格式的,一但被读取就会自动释放。

FIFO的创建

int mkfifo( const char *pathname, mode_t mode);

依赖的头文件:

#include <sys/types.h>
#include <sys/stat.h>

参数:
  pathname:FIFO的路径
  mode:以何种方式打开
返回值:成功返回0,失败返回-1

FIFO的读写

  因为FIFO也是文件的一种,所以IO函数都可以作用于FIFO。 下面我们讨论几种情况:

情况一:不指定O_NONBOLCK时只读和只写open的情况

以只读的方式打开:若系统中没有以写方式打开的进程,则该打开一直阻塞等待,直到其他进程以写的方式打开的时候,该只读才会继续执行、正常打开文件。
以只写方式打开:若系统中没有以读方式打开的进程,则该打开一直阻塞等待,直到其他进程以读的方式打开的时候,该只写才会继续执行、正常打开文件。
注意:这里只是讨论的打开的情况还不涉及下面的操作,即代码中的 open(“my_fifo”, O_WRONLY); 这一句。
下面我们用图片进行详解。先执行read和先执行write出现的现象一样,我们以先执行read为例。首先先附上代码:
write文件:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	char send[100] = "Hello I love you";
	
	ret = mkfifo("my_fifo", S_IRUSR|S_IWUSR);
	if(ret != 0)
	{
		perror("mkfifo");
	}
	printf("before open\n");
	fd = open("my_fifo", O_WRONLY);
	if(fd<0)
	{
		perror("open fifo");
	}
	printf("after open and before read\n");
	write(fd, send, strlen(send));
	printf("write to my_fifo buf=%s\n",send);
	return 0;
}

read文件:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	char recv[100];
	
	ret = mkfifo("my_fifo", S_IRUSR|S_IWUSR);
	if(ret != 0)
	{
		perror("mkfifo");
	}
	printf("before open\n");
	fd = open("my_fifo", O_RDONLY);
	if(fd<0)
	{
		perror("open fifo");
	}
	printf("after open and before read\n");
	bzero(recv, sizeof(recv));
	read(fd, recv, sizeof(recv));
	printf("read from my_fifo buf=[%s]\n",recv);
	return 0;
}

首先我们先运行read,运行结果如t图1所示,此时只输出了“before open”,并没有输出“after open and before read”,所以程序阻塞在了“ fd = open(“my_fifo”, O_RDONLY);”这一句,原因是此时没有以写的方式打开的进程。
在这里插入图片描述
然后我们运行write,运行了此程序以后,系统中就有了以写方式打开的进程,read就会继续运行,并且获取到管道的内容进行输出。运行结果如图二所示:
在这里插入图片描述

情况二:不指定O_NONBOLCK时read的情况
小情况1:FIFO没有数据时read()会进入阻塞

当FIIFO中没有数据,read会进入阻塞状态,这里我们进行实验,为了保证两个程序都能运行,我们这里先打开write程序,然后打开read程序,二者以只读或者只写的方式打开,然后再write中我们先不发送数据,人为的延迟10秒,这时我们会看到read程序进入阻塞装态,十秒延迟以后就变成运行态的。write的代码如下,read的代码不变。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


void main()
{
        int fd,ret;
        ret = mkfifo("my_fifo", S_IRUSR|S_IWUSR);
        if(ret != 0)
        {
                perror("mkfifo err\n");
        }
        printf("打开写文件之前\n");
        fd = open("my_fifo",O_WRONLY);
        if(fd<0)
        {
                perror("open error \n");
        }

        printf("打开写文件之后,进入10秒延迟\n");
        sleep(10);
        char send[100] = "helloworld";
        write(fd,send,sizeof(send));

}

运行结果:
在这里插入图片描述

小情况2:write程序退出以后read()变为执行

通信过程中若写进程先退出了,此时read()不阻塞,但是若写进程又重新执行,那么read又会阻塞。
实验的代码如下:
write

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	int fd;
	char send[100] = "Hello I love you";
	
	fd = open("my_fifo", O_WRONLY);
	if(fd<0)
	{
		perror("open fifo");
	}
	write(fd, send, strlen(send));
	printf("write to my_fifo buf=%s\n",send);
	while(1);
	return 0;
}

read:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	
	ret = unlink("my_fifo");	//删除上次运行后残留的fifo
	if(ret<0)
	{
		perror("unlink fifo");
	}
	ret = mkfifo("my_fifo", S_IRUSR|S_IWUSR);
	if(ret != 0)
	{
		perror("mkfifo");
	}
	fd = open("my_fifo", O_RDONLY);
	if(fd<0)
	{
		perror("open fifo");
	}
	while(1)
	{
		char recv[100];
		
		bzero(recv, sizeof(recv));
		read(fd, recv, sizeof(recv));
		printf("read from my_fifo buf=[%s]\n",recv);
		sleep(1);
	}
	return 0;
}

1.首先我们将两个程序都跑起来,可以看到read中第一次可以正确读取,但是读取之后FIFO变为空,read()就会变为阻塞状态。
在这里插入图片描述
2.然后将write程序关闭,此时read()变为执行状态,但是或取得数据为空。
在这里插入图片描述
3.再将write程序启动,正确读取一次以后又会变成阻塞状态。
在这里插入图片描述

情况三:指定O_NONBLOCK

1.先以只读方式打开,若没有其他进程为写而打开,则打开成功且不阻塞
2.先以只写方式打开,若没有其他进程为读而打开,则打开失败,返回-1,说明我们要先打开读方式,再打开写方式。
3.read(),write()方法运行时都不阻塞。
write:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h> 
#include <stdlib.h>
void main()
{
	int fd,isTrue;
	char arg []="helliworld";
	isTrue = mkfifo("my_fifo",0777);
	if(isTrue != 0){
		perror("管道创建失败\n");
	}

	fd = open("my_fifo",O_WRONLY|O_NONBLOCK);
	if(fd < 0)  
	{  
		perror("open fifo\n");
	        exit(-1);
	}
	write(fd,arg,sizeof(arg));
	printf("发送成功\n");
	while(1);
	
}

read:

#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h> 
#include <stdlib.h>
void main()
{
	int fd,isTrue;
	char arg [100];
	isTrue = mkfifo("my_fifo",0777);
	if(isTrue != 0){
		perror("管道创建失败\n");
	}

	fd = open("my_fifo",O_RDONLY|O_NONBLOCK);
	if(fd < 0)  
	{  
		perror("open fifo\n");
	        exit(-1);
	}

	while(1)
	{
		read(fd,arg,sizeof(arg));
		printf("获取到的内容[%s]\n",arg);
		sleep(1);
	}
}

我们先运行read,再运行write,运行结果如下,可以看到没发送数据的时候read()也不会进行阻塞,只不过读到的数据是空。
在这里插入图片描述

作业

1.调用pipe()函数,实现从子进程发送“hello world”到父进程,父进程打印输出的功能。
解析:只需要创建pipe管道,然后创建一个子进程,在子进程中向管道写,父进程中从管道读

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <stdlib.h>

void main()
{
	int filedes[2];
	pid_t pid;
	char buff2[20],buff1[]="helloworld";
	if(pipe(filedes)<0){
		perror("pipe error\n");
		exit(-1);
	}	

	pid = fork();

	if(pid<0)
	{
		perror("fork error \n");
		exit(-1);
	}

	if(pid == 0)
	{
		printf("子进程向管道写入数据\n");
		write(filedes[1],buff1,strlen(buff1));

	}else{
		wait(NULL);
		memset(buff2,0,sizeof(buff2));
		read(filedes[0],buff2,sizeof(buff2));
		printf("父进程读出数据[%s]\n",buff2);
	}

}

2.调用dup()函数,通过printf()函数将字符串“hello world”写入到指定文本文件。
解析:文件描述符表中默认的0 1 2 都是打开的,此时我们打开文件文件,就会生成文件描述符3,此时再把1关掉,表中就剩余0 2 3 ,再通过dup(),返回一个最小的即 1 ,就把1重定向到了3即文件。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <stdlib.h>

void main()
{
	int fd;
	fd=open("test",O_CREAT|O_RDWR,0777);
	if(fd<0){
		perror("open error \n");
		exit(-1);
	}
	close(1);
	dup(fd);
	printf("hello world\n");


}

3.通过命名管道,以非阻塞的方式,实现两个进程间收发字符串“hello world”的功能。
解析:只需要指定O_NONBLOCK打开,然后read中读取数据,write中写“hellowoord”就可以了。程序见上面【指定O_NONBLOCK】部分。

谢谢大家的观看,如有错误请指正,谢谢!CSDN记录成长!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值