一、系统调用
由操作系统实现并提供给外部应用程序的编程接口(Application Programming Interface,API),用户程序可以通过这个特殊接口来获得操作系统内核提供的服务
系统调用和库函数的区别:
系统调用(系统函数) 内核提供的函数
库调用 程序库中的函数
错误处理函数
errno用于记录系统的最后一次错误代码,返回一个int值(错误码),在errno.h中定义,不同的错误码表示不同的含义,新建errno.c如下:
#include<stdio.h>
#include<errno.h>
#include<string.h>int main(void)
{FILE *fp = fopen("txt","r");//打开一个不存在的文件if (NULL == fp){printf("fopen failed\n");printf("errno:%d\n",errno);//打印errno返回的错误码printf("fopen:%s\n",strerror(errno));//使用strerror函数来解释错误码return 1;}return 0;
}
编译再执行可得如下结果:
fopen failed
errno:2
fopen:No such file or directory
虚拟地址空间:
文件描述符:
- 当我们打开文件或者新建文件时,系统会返回一个文件描述符用来指定已打开的文件,这个文件描述符相当于这个已打开文件的标号,操作这个文件描述符就相当于操作这个描述符所指定的文件;
- 程序运行起来后每个进程都有一张文件描述符的表,标准输入、输出,标准错误输出,对应的文件描述符0、1、2就记录在表中,程序运行起来后这三个文件描述符是默认打开的;
文件描述符是指向一个文件结构体的指针
进程控制块(PCB):本质---结构体
FILE结构体:主要包含文件描述符、文件读写位置、IO缓冲区三部分内容
最大打开文件数:一个进程默认打开文件的个数1024
命令查看:ulimit -a 查看open files 对应值。默认为1024
可以使用ulimit -n 4096 修改
cat /proc/sys/fs/file-max可以查看该电脑最大可以打开的文件个数。受内存大小影响。
二、常用文件IO函数
1.open函数
#include <sys/types.h>
#include <sys.stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: #include <fcntl.h>
O_RDONLY|O_WRONLY|O_RDWR
O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
mode:这个参数只有在文件不存在时有效,指新建文件时指定文件的权限
取值8进制数,用来描述文件的访问权限。 rwx 0664
创建文件最终权限 = mode & ~umask
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
flags必选项:
O_RDONLY 以只读的方式打开
O_WRONLY 以只写的方式的打开
O_RDRW 以可读、可写的方式打开
可选项,和必选项进行位或(|)
O_CREAT 文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL 如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC 如果文件存在,则清空文件内容
O_APPEND 写文件时,数据添加到文件末尾
O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O
打开dict.cp,如果不存在则创建,并添加权限,如果存在则清空内容:
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>int main(int argc,char *argv[])
{int fd;open("./dict.cp",O_RDONLY | O_CREAT | O_TRUNC,0644);//rw-r--r--printf("fd = %d\n",fd);close(fd);return 0;
}
对于存在mydir目录以及不存在mydir目录执行以下程序:
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>int main(int argc,char *argv[])
{int fd;open("mydir",O_WRONLY);printf("fd = %d,errno = %d:%s\n",fd,errno,strerror(errno));close(fd);return 0;
}
mydir目录存在输出为:
fd = 21991,errno = 21:Is a directory
mydir目录不存在输出为:
fd = 22002,errno = 2:No such file or directory
2.close函数
#include <unistd.h>
int close(int fd);
功能:
关闭已打开的文件
参数:
fd:文件描述符,open()的返回值
返回值:
成功:0
失败:-1,并设置errno
3.write函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
把指定数目的数据写到文件(fd)
参数:
fd:文件描述符
buf:数据首地址
count:写入数据的长度(字节)
返回值:成功:实际写入数据的字节个数
失败:-1
4.read函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:
把指定数目的数据读到内存(缓冲区)
参数:
fd:文件描述符
buf:内存首地址
count:读取的字节个数
返回值:
成功:实际读取到的字节个数
失败:-1
用read和write实现一个copy函数:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>int main(int argc,char *argv[])
{char buf[1024];int n = 0;int fd1 = open(argv[1],O_RDONLY);//readif (fd1 == -1){perror("open argv1 error");exit(1);}int fd2 =open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0644);if (fd2 == -1){perror("open argv2 error");exit(1);}while((n = read(fd1,buf,1024)) != 0){if (n < 0){perror("read error");break; }write(fd2,buf,n);}close(fd1);close(fd2);return 0;
}
5.lseek函数
#include <sys/types.h>#include cunistd.h>
off_t lseek(int fd,off_t offset,int whence);功能:
改变文件的偏移量(读写位置)参数:
fd:文件描述符offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节SEEK_CUR:从当前位置移动offset个字节
SEEK__END:从文件未尾移动offset个字节
返回值:
若lseek成功执行,则返回新的偏移量如果失败,返回-1
lseek允许超过文件结尾设置偏移量,文件会因此被拓展。
使用lseek获取文件大小:
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>int main(int argc,char *argv[])
{int fd = open(argv[1],O_RDWR);if (fd == -1){perror("read error");exit(1);}int lenth = lseek(fd,0,SEEK_END);//获取文件大小printf("file size:%d\n",lenth);close(fd);return 0;
}
make之后执行 ./lseek_test dict.c 可得到dict.c的文件大小
out:
file size:48
使用lseek扩展文件大小:
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>int main(int argc,char *argv[])
{int fd = open(argv[1],O_RDWR);if (fd == -1){perror("read error");exit(1);}int lenth = lseek(fd,52,SEEK_END);//扩展文件大小printf("file size:%d\n",lenth);write(fd,"a",1);close(fd);return 0;
}
make之后执行 ./lseek_test dict.c 可得到dict.c的文件大小,再执行ls -l dict.c得到扩展之后的文件大小,输出分别为:
file size:100
-rw-rw-r-- 1 *** *** 101 *** ** *** dict.c
应用场景:
1. 文件的“读”、“写”使用同一偏移位置。
2. 使用lseek获取文件大小(返回值接收)
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须【引起IO操作】(即write)。
使用 truncate 函数,直接拓展文件。
int ret =truncate("dict.cp”,250):
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>int main(int argc,char *argv[])
{//open/lseek(fd,249.SEED_END)/write(fd,"\0",1);int ret = truncate("dict.cp",200);printf("ret = %d\n",ret);return 0;
}
起始cidt.cp文件大小为0,经过以上代码扩展之后,cidt.cp文件大小为200
三、系统调用和库函数比较---预读入缓输出
fputc/fgetc实现:
int main(void){FILE *fp,*fp_out;int n = 0;fp = fopen( " hello.c" , "r");if(fp == NULL){perror( " fopen error" );exit( 1);}fp_out = fopen ( "hello.cp" ,"w" );if(fp_out =NULL){perror( "fopen error" );exit(1);}while((n = fgetc(fp))!= EOF){fputc(n, fp_out) ;}fclose(fp);fclose(fp_out);return 0;
}
read/write实现:
int main( int argc, char *argv[])
{char buf[ 1];int n = 0;int fd1 = open(argv[1],0_RDONLY);int fd2 = open(argv[2],O_RDWR|0_CREAT|0_TRUNC,0664);while((n = read (fd1,buf,1)) != 0){write(fd2, buf, n);}close(fd1);close(fd2);return 0;
}
结果表明:read/write速度慢
原因分析:
- read/write这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。
- fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少
预读入,缓输出机制。所以系统函数并不是一定比库函数牛逼,能使用库函数的地方就使用库函数。
- 标准IO函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。
四、阻塞和非阻塞
产生阻塞的场景:读设备文件。读网络文件的属性。(读常规文件无阻塞概念)
/dev/tty -- 终端文件。
open("/dev/tty", O_RDWR | O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)
更改非阻塞读取终端——超时设置
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n" int main(void)
{ //打开文件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); //轮询读取char buf[10]; for (i = 0; i < 5; i++){ n = read(fd, buf, 10); if (n > 0) { //说明读到了东西 break; } if (errno != EAGAIN) { //EWOULDBLOCK 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;
}
五、传入传出参数
传入参数:
1. 指针作为函数参数。
2. 同常有const关键字修饰。
3. 指针指向有效区域, 在函数内部做读操作。
传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。
3. 在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。
传入传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间有实际意义。
3. 在函数内部,先做读操作,后做写操作。
4. 函数调用结束后,充当函数返回值。