阻塞和非阻塞

系统编程

系统调用: 内核提供的函数

库调用: 程序库中的函数

1.用read和write实现的cp指令

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];

	int n = 0;

	int fd1 = open(argv[1], O_RDONLY);
	if(fd1 == -1){
		perror(“open argv[1] error”);
		exit(1);
	}
	int fd2 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0664);/* 创建文件最终权限 mode &~umask, umask = 0002 */
	if(fd2 == -1){
		perror(“open argv[2] error”);
		exit(1);
	}
	
	while((n = read(fd1, buf, 1024)) != 0){
		write(fd2, buf, n);
	}

	close(fd1);
	close(fd2);

	return 0;

}

read/write 实现的 copyfgetc/fputc 实现的 cp 对比:

在这里插入图片描述

原因分析:

read/write 这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。

fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少

预读入,缓输出机制。

所以系统函数并不是一定比库函数牛逼,能使用库函数的地方就使用库函数。

标准 IO 函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。

文件描述符

在这里插入图片描述
文件描述符是指向一个文件结构体的指针

PCB 进程控制块:本质 结构体。

成员:文件描述符表。

文件描述符: 0/1/2/3/4... /1023 表中可用的最小的。

0 - STDIN_FILENO

1 - STDOUT_FILENO

2 - STDERR_FILENO

最大打开文件数:一个进程默认打开文件的个数1024.

命令查看 ulimit -a 查看 open files 对应值。 默认为1024

也可以通过修改系统配置文件永久修改该值,不建议。

阻塞和非阻塞

阻塞、非阻塞: 是设备文件、网络文件的属性。

阻塞请求:A调用B ,A一直等着B的返回,别的事情什么也不干。
非阻塞请求:A调用B,A不用一直等着B的返回,先去忙别的事情了。

产生阻塞的场景。 读设备文件。读网络文件。(读常规文件无阻塞概念。)

/dev/tty – 终端文件。

open("/dev/tty", O_RDWR|O_NONBLOCK) — 设置 /dev/tty 非阻塞状态。 (默认为阻塞状态)

阻塞状态,从标准输入读,写到标准输出

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

int main(){
	char buf[10];
	int n;

	n = read(STDIN_FILENO, buf, 10);
	if (n < 0){
		perror("read STDIN_FILENO");
		exit(1);
	}
	write(STDOUT_FILENO, buf, n);

	return 0;
}

执行程序,会发现程序再阻塞等待输入。

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n"

int main(){
	char buf[10];
	int fd, n, i;

	fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
	if (fd < 0){
		perror("open /dev/tty");
		exit(1);
	}
	printf("open /dev/tty ok... %d\n", fd);

	for (i = 0; i < 5; i++){
		n = read(fd, buf, 10);
		if (n > 0){
			break;   
		}
		if (errno != EAGAIN){
			perror("read /dev/tty");
			exit(1);
		}else {
			write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
			sleep(2);
		}
	}

	if (i == 5){
		write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));

	} else{
		write(STDOUT_FILENO, buf, n);
	}

	close(fd);

	return 0;
}

运行结果为

ymyy@ymyy-virtual-machine:~/systemcode$ ./nonblock_readtty 
open /dev/tty ok... 3	
try again	
try again	
try again	
try again	
try again	
time out

fcntl

fcntl用来改变一个【已经打开】的文件的访问控制属性,重点掌握两个参数的使用, F_GETFL, F_SETFL

fcntl:

int (int fd, int cmd, …)

fd 文件描述符 cmd 命令,决定了后续参数个数

int flags = fcntl(fd, F_GETFL);

flags |= O_NONBLOCK

fcntl(fd, F_SETLF, flags);

获取文件状态:F_GETFL

设置文件状态:F_SETFL

终端文件默认阻塞读,用fcntl将其改为非阻塞读。

#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#define MSG_TRY "try again\n"

int main(void)
{
	char buf[10];
	int flags, n;

	flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息
	if(flags == -1){
		perror("fcntl error");
		exit(1);
	}
	flags |= O_NONBLOCK;

	int ret = fcntl(STDIN_FILENO, F_sETF, flags);//设置终端文件状态
	if(ret == -1){
		perror("fcntl error");
		exit(1);
	}
}

结果为: 非阻塞读取

ymyy@ymyy-virtual-machine:~/systemcode$ ./fcntl 
try again
try again
try again
try again
try again
24452
try again
24452

##lseek函数

off_t lseek(int fd, off_t offset, int whence);

参数:

fd:文件描述符

offset: 偏移量,就是将读写指针从 whence 指定位置向后偏移 offset 个单位

whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END

返回值:

成功:较起始位置偏移量

失败: -1 errno

应用场景:

  1. 文件的“读” 、 “写” 使用同一偏移位置。

  2. 使用 lseek 获取文件大小

  3. 使用 lseek 拓展文件大小:要想使文件大小真正拓展,必须引起 IO 操作。

使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);
lseek 示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾

代码如下:

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fscntl>

int main(int argc, char *argv[]){
	int fd, n;
	char msg[] = "It's a test for lseek";
	char ch;

	fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);
	if(fd < 0){
		perror("open lseek.txt error");
		exit(1);
	}
	
	write(fd, msg, strlen(msg)); // 使用fd对打开的文件进行写操作,文件读写位置位于文件结尾处

	lseek(fd, 0, SEEK_SET); //修改文件指针位于文件开头

	while((n = read(fd, &ch, 1))){
		if(n < 0){
			perror("read error");
			exit(1);
		}
		write(STDOUT_FILENO, &ch, n);
	}

	close(fd);

	return 0;

}

ymyy@ymyy-virtual-machine:~/systemcode$ ./lseek_test 
It's a test for lseekymyy@ymyy-virtual-machine:~/systemcode$ 
ymyy@ymyy-virtual-machine:~/systemcode$ 

用lseek的偏移来读取文件大小:

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
	int fd = open(argv[1], O_RDWR);
	if(fd == -1){
		perror("open error");
		exit(1);
	}

	int lenth = lseek(fd, 0, SEEK_END);
	printf("file size: %d\n", lenth);

	close(fd);
	return 0;
}

ymyy@ymyy-virtual-machine:~/systemcode$ make
gcc lseek_test1.c -o lseek_test1
ymyy@ymyy-virtual-machine:~/systemcode$ ./lseek_test1 ./fcntl.c
file size: 1041
ymyy@ymyy-virtual-machine:~/systemcode$ ll fcntl.c
-rw-rw-r-- 1 ymyy ymyy 1041 6月   8 23:27 fcntl.c

修改偏移量,可以扩展文件。但是要使文件大小真正扩展,必须IO操作。拓展文件直接使用 truncate,简单粗暴:使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);

##传入传出参数

传入参数:

  1. 指针作为函数参数。

  2. 同常有 const 关键字修饰。

  3. 指针指向有效区域, 在函数内部做读操作。

传出参数:

  1. 指针作为函数参数。

  2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。

  3. 在函数内部,做写操作。

  4. 函数调用结束后,充当函数返回值。

传入传出参数:

  1. 指针作为函数参数。

  2. 在函数调用之前,指针指向的空间有实际意义。

  3. 在函数内部,先做读操作,后做写操作。

  4. 函数调用结束后,充当函数返回值

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值