04-Linux系统编程-第01天(文件IO、阻塞非阻塞)
03-系统函数
系统编程章节大纲
1 文件I/O
2 文件系统
3 进程
4 进程间通信
5 信号
6 进程间关系
7 守护进程
8 线程
9 线程同步
10 网络基础
11 socket编程
12 高并发服务器
13 异步I/O
14 shell编程
Man page
1 命令和可执行程序
2 系统调用
3 标准库
系统调用本质就是函数
man 2 printf
man
严格来说write是 sys_write的浅封装
sys_write才是真正的系统调用 不过一般我们就说write是系统调用
内核:操作系统内部核心程序
内核里包含一些驱动程序
printf 然后 系统调用write函数 驱动显卡输出
int 返回一个文件描述符
vim里查看man手册 K
光标移动到open 然后 2 K
fcntl.h 定义O_RDONLY宏
unistd.h 声明open系统调用
stdio.h 声明printf
只有当第二个参数为O_CREAT的时候 才需要在第三个参数指定权限
(上面意思是 读 如果没有就创建)
最终创建出的文件的权限受第三个参数决定和umask一起决定
直接给结论:mode(第三个参数) & ~umask
mode 与 (umask取反)
比如umask是002 那么umask取反就是775
775与777 就是775
以只读方式打开 如果文件不存在就创建 如果文件已经存在就截断
(截断:把文件里内容清除掉)
文件描述符返回-1就是错了
04-read_write
想明白文件描述符本质
要先明白PCB
.bss: 未初始化的全局变量 初始化为0的全局变量 static修饰(静态变量)的未初始化的或初始化为0的变量
data:初始化为非0的全局变量 static修饰(静态变量)的初始化为非0的变量
rodata:常量、只读全局变量
stack:局部变量
段错误:
1. 对只读区域进程写操作。 char *p = "hello"; p[0] = 'H';
2. 对非访问区域进行读写操。 地址1000
3. stack空间耗尽。 函数内部 int arr[N]= {1}; #define N 100000000
当执行./a.out的时候操作系统 会虚拟出这样的虚拟地址
一个进程可能实际只使用几k 。 但是可用地址范围有这么多
pcb 本质是一个结构体
里面有一个成员变量是指针
指针指向一个指针数组(可以理解为一个字符指针数组 这个数组里面都是指针)
(int *p [N])
每一个指针指向字符串 严谨的来说 指向的还是结构体
(… 不明觉厉)
数组下标就理解为文件描述符
(实际上是一个指针指向结构体)
open一个文件时 内核会维护一个结构体让我操作该文件,
man 2 read
read和write函数的使用:
read和write函数的使用:
05-cp命令实现
copy.c:
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(int argc,char * argv[])
{
char buf[1024];
int ret = 0;
int fd1 = open(argv[1],O_RDONLY);
int fd2 = open(argv[2],O_RDWR |O_TRUNC|O_CREAT,0664);
while((ret = read(fd1,buf,sizeof(buf))) != 0 )
{
write(fd2,buf,ret);
}
close(fd1);
close(fd2);
}
06-预读入缓输出
每当执行一个a.out的时候 在kernal里就会有一个与之唯一对应的PCB进程控制块
注意:
read 返回的是实际读到的字节数
write的时候 第三个参数应该传这个字节数 而不是像read一样是buf的大小
C标库一次读一个字符
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp,*fp_out;
int n;
fp = fopen(“dict.txt”,”r”);
if(fp == NULL) {
perror(“fopen error”);
exit(1)
}
fp_out = fopen(“dict.cp”,”w”);
if(fp == NULL) {
perror(“fopen error”);
exit(1);
}
while((n = fgetc(fp))!=EOF) {
fputc(n,fp_out);
}
fclose(fp);
fclose(fp_out);
return 0;
}
系统调用一次读一个字符:
#include <stdlib.h>
#include <errno.h>
#define N 1
int main(int argc,char * argv[])
{
int fd,fd_out;
int n;
char buf[N];
fd = open(“dict.txt”,O_RDONLY);
if(fd < 0){
perror(“open dict.txt error”);
exit(1);
}
fd_out = open(“dict.cp”,O_WRONLY|O_CREAT|O_TRUNC,0644);
if(fd<0){
perror(“open dict.cp error”);
exit(1);
}
close(fd);
close(fd_out);
return 0;
}
实际跑了一遍发现用read函数一个字节一个字节读还不如用fgetc速度块..
用系统调用不仅没快 反而奇慢
知识点:
预读入缓输出
预读入缓输出机制
我们认为用户程序用户定义buf直接使用系统调用write函数 跳过了标准库函数进入kernal
让kernal写到磁盘文件上了
实际上不是这样的:
实际上在内核中默认也维护了一个缓冲区 默认是4096byte
内核为了提高效率会一次性等缓冲区满以后再刷到磁盘上
当自己写的write函数写的时候 实际上没有写到磁盘上,而是在内核的缓冲区
这种机制称为缓输出
C标准库函数 自己带着一个缓冲区
fgetc fputc。。。 这些内部实现包含缓冲区
fgetc读字符会放到自己的缓冲区里
读4096字节放到自己的蓝色框缓冲区里
然后向下系统调用 write
把数据写到内核的缓冲区
然后再刷到磁盘上
之所以慢是因为
从用户区切换到内核区这个时间消耗很大。
直接系统调用write的方法 从用户区到内核区的切换工作耗了很多时间 每次一个字节就要切换一次
用fgetc fputc 只切换了一次
下面验证一下…:
strace 命令跟踪程序运行时间的系统调用
结果: 每次读写字节都切换
结果:
每次4096个 总的切换次数少很多
预读入:
一次性把缓冲区读满
需要的时候就从缓冲区取
既然效率这么低 那么学习系统调用write函数有啥用 直接用标库函数不就得了..?
答案: 比如qq聊天 要求即时性, 不是要等4096个byte再发过去
emm 把刚才write函数的define N 1 改成 1024 就能发现快很多了
把数据交给内核 内核什么时间写到磁盘上由内核决定 交给它内部的I/O优化算法。
07-错误处理函数
建议对所有的系统调用做错误处理
不使用printf打印错误了
使用perror
(perror不需要加\n)
结果:
还有一个strerror(不常用
08-阻塞非阻塞
终端设备是指: 0 标准输入 1 标准输出 3 标准错误
当cat的时候就阻塞了
stdin stdout stderror对应的文件都是设备文件 dev下面的tty
下面看三个小程序
*
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:
正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。
就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
阻塞读终端: 【block_readtty.c】
非阻塞读终端 【nonblock_readtty.c】
非阻塞读终端和等待超时 【nonblock_timeout.c】
注意,阻塞与非阻塞是对于文件而言的。而不是read、write等的属性。read终端,默认阻塞读。
总结read 函数返回值:
1. 返回非零值: 实际read到的字节数
2. 返回-1: 1):errno != EAGAIN (或!= EWOULDBLOCK) read出错
2):errno == EAGAIN (或== EWOULDBLOCK) 设置了非阻塞读,并且没有数据到达。
3. 返回0:读到文件末尾
1.阻塞读终端:
block_readtty.c:
2.非阻塞读终端::
nonblock_readtty.c
正常情况下./a.out tty是默认打开的 但是非阻塞的情况下 重新打开了tty文件 目的是给他指定以O_NONBLOCK非阻塞方式打开终端
定义tryagain标签,后面有goto语句
read函数返回小于0 (即-1) 时候已经出错了 但是其实出错了还可以进一步进行判断errorno
如果errno是 EAGAIN或=EWOULDBLOCK 说明当前是以非阻塞的方式读终端 而恰巧终端没有数据
如果errno不是EAGAIN说明是出错了
如果是EAGAIN说明read函数没有出错 只不过读的文件是一个设备文件而当前没有数据递达 所以就sleep了3秒
非阻塞读终端 每隔3秒弹出try again
3.非阻塞读终端 等待超时
nonblack_timeout.c:
man 2 read
正常情况下read函数返回的是你实际读到的字节数
09-lseek
标准库里讲过fseek 设置文件的读写位置
linux中可以使用lseek
lseek.c:
有个问题 当我write完之后 光标指向结尾了 这个时候read不出来了
所以要使用lseek
把指针再指向开头
lseek(fd,0,SEEK_SET)
注意:一个空文件lseek位置以后必须进行一下i/o操作 才会发生实质性的拓展 否则lseek没啥用
(如果不write就没啥用了
vi 查看一下生成的文件
前面这些填充我们称为 文件空洞 其实就是0
应该用od看 不是vi
od –tcx lseek.txt
lseek也可以用来获取文件大小
int len = lseek(fd,0,SEEK_END) // 这个就是文件大小
1 #include <string.h>
2 #include <fcntl.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6
7
8
9 int main(void)
10 {
11 int fd,n;
12 char msg[] = "It's a test for lseek\n";
13 char ch;
14
15
16 fd = open("lseek.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
17
18 if(fd<0){
19 perror("open lseek.txt error");
20 exit(1);
21 }
22
23 int ret = lseek(fd,99,SEEK_SET);
24 if(ret == -1){
25 perror("lseek error");
26 exit(1);
27 }
28 write(fd,"a",1);
29
30 close(fd);
31
32 return 0;
10-fcntl
fcontrol
文件描述符对应着一个结构体 结构体内部控制着访问属性
(man 2 fcntl
fgetfileflag 获取当前文件信息
fsetfileflag 设置当前文件信息
位或 或等于
位图
bitmap
当描述一个文件属性的时候 通过一个整形数的二进制位来描述
i/o control
11-ioctl和传入传出参数
io control
iocntl.c:
TIOCGWINSZ
terminal IO windowsize
虚拟终端窗口大小
locate sys/ioctl.h
sudo grep –r “TIOCGWINSZ” /usr
传出参数 这个函数调用一结束 size结构体就有值了:
通过io control 拿到当前窗口占用的行值和列值
获取当前设备文件的行宽和列宽
举例strcpy(char *dest, const char *src)
*dest就是传出参数
04-Linux系统编程-第01天(文件IO、阻塞非阻塞)的更多相关文章
- Linux系统编程(1)——文件与I/O之C标准I/O函数与系统调用I/O
Linux系统的I/O也就是一般所说的低级I/O--操作系统提供的基本IO服务,与os绑定,特定于Linux平台.而标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头 ...
- Linux系统编程(2)——文件与IO之系统调用与文件IO操作
系统调用是指操作系统提供给用户程序的一组"特殊"接口,用户程序可以通过这组"特殊"接口来获得得操作系统内核提供的特殊服务.在linux中用户程序不能直接访部内核 ...
- linux系统编程快速定位头文件的技巧之强大的grep命令
这个技巧来自于我的实际开发碰到的: inet_addr这个函数用于把ip地址转成网络字节序,他的原型:in_addr_t inet_addr(const char *cp); 返回值为一个in_add ...
- Linux系统编程(4)——文件与IO之ioctl函数
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数.所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率.马达的转速等等.它的参数个数如下:int ioctl(int ...
- Linux系统编程(5)——文件与IO之mmap函数
mmap系统调用它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作.而Posix或系统V的共享内存IPC则纯粹用于共享目的,mmap()实现共享内存也是其主要应用之一. ...
- Linux系统编程(3)——文件与IO之fcntl函数
linux文件I/O用:open.read.write.lseek以及close函数实现了文件的打开.读写等基本操作.fcntl函数可以根据文件描述词来操作文件. 用法: int fcntl(int ...
- Linux系统编程---实现目录或者文件拷贝
关于拷贝文件,前面写过一个例子:点击打开链接 ,可以看看,实现cp命令. 这次我们实现一个目录和文件的拷贝,综合点. #include <stdio.h> #include <fcn ...
- 《Linux系统编程(第2版)》
<Linux系统编程(第2版)> 基本信息 作者: (美)Robert Love 译者: 祝洪凯 李妹芳 付途 出版社:人民邮电出版社 ISBN:9787115346353 上架时间:20 ...
- Linux系统编程温故知新系列 --- 01
1.大端法与小端法 大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法 小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法 网际协议使用大端字节序来传送TCP分节中的多字节整数 ...
- Linux C 程序 文件操作(Linux系统编程)(14)
文件操作(Linux系统编程) 创建一个目录时,系统会自动创建两个目录.和.. C语言实现权限控制函数 #include<stdio.h> #include<stdlib.h> ...
随机推荐
- ustc 1117
无根树同构 有两种方法,一种是确定其中一棵树,另一棵树枚举根节点. 一种是,利用拓扑排序,先确定其中一棵树.另一棵树,若拓扑后剩两个节点,则枚举这两个节点为根结点,否则,只需做一次.注意,无根树节点入 ...
- 64位BASM学习随笔(一)
64位BASM学习随笔(一) Delphi的BASM一直是我最喜爱的内嵌汇编语言,同C/C++的内联汇编相比,它更方便,更具灵活性,由于C/C++的内联汇编仅仅能是或插入式的汇编代码,函数花括号 ...
- MyBatis -- 对表进行增删改查(基于注解的实现)
1.MyBatis对数据库表进行增/删/改/查 前一篇使用基于XML的方式实现对数据库的增/删/改/查 以下我们来看怎么使用注解的方式实现对数据库表的增/删/改/查 1.1 首先须要定义映射sql的 ...
- n阶导函数存在与n阶可导的区别
1.f(x)n阶导函数存在 <=======> f(n)(x)存在 指的是在某个区间内有定义 2.f(x)n阶可导根据题意可以有两种不同的解释: ①.题目中说的是在某点即在x=x0处n ...
- Android+Jquery Mobile学习系列(9)-总结和代码分享
经过一个多月的边学习边练手,学会了Android基于Web开发的毛皮,其实开发过程中用Android原生API不是很多,更多的是HTML/Javascript/Css. 个人觉得基于WebView的J ...
- 如何修改vos2009/vos3000的web端口?
vos 2009. VOS 3000 2120 -2138版本在这里 /usr/apache-tomcat-5.5.15/conf 编辑 server.xml 找到 <!-- Define a ...
- Rocky(模拟)
http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2718 题意:如果没有障碍就按原方向直走,否则就 ...
- 1961 躲避大龙(dfs)
1961 躲避大龙 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 你早上起来,慢悠悠地来到学校门口,发现已经是 ...
- 原生JS---4
原生js学习笔记4——BOM操作 什么是DOM DOM:Do 1. js的组成部分 2. 一套标准,目前有DOM1和DOM2这两种标准 我们可以使用DOM操作来操作页面中的元素. DOM节点 子节点 ...
- Redis(二)-Win系统下安装
下载地址:https://github.com/MSOpenTech/redis/releases. Redis 支持 32 位和 64 位.这个需要根据你系统平台的实际情况选择,这里我们下载 Red ...