1. Linux系统调用及用户编程接口(API)
- linux中为了保护内核空间,将程序的运行空间分为内核空间和用户空间。我们当然是不希望操作系统的数据被随意的篡改和访问,有可能造成十分严重的后果,所以操作系统对内存做了区分,核心态(0),服务态(1,2),用户态(3),数值越小,级别越高,底级别进程无权访问高级别的内存区域,因此隔离了系统程序和用户程序,提高了操作系统的安全性。
- 系统调用:是用户进入内核的一个接口,它不是内核层的,但是它可以调用内核函数,我理解为在写c文件时候类似.h文件这种概念。
- 用户编程接口API:主要就是在给用户层面提供函数接口。可以是与内核无关的,直接提供调用的函数的接口,如果是与内核相关的,API内部就会对系统调用进行封装,最后也是给用户一个接口,让用户只能在用户空间为起点一层一层调用内核空间的函数,确保调用的安全性。
参考链接:系统调用、API之间的关系
2. Linux中文件及文件描述符概述
在使用系统调用函数时,遇到不熟悉函数功能时可以用man命令查看。常用的几个命令:
1 普通的命令
2 内核调用的函数与工具
3 常见的函数与函数库
参考链接:linux中的man命令总结
使用系统调用函数时,多用man命令。如creat函数,进去把所需要的.h头文件赋值粘贴出来。确保头文件的正确性。
man手册中有下划线的是函数参数,所以在查看手册时候可以寻找下划线,找到后看具体参数描述。
在打开或者创建一个文件时候,这时已经调用了内核函数,内核会给一个返回值。称其为文件描述符,和用户区中正常的函数返回值的概念理解起来有些不同。这是个索引值,相当于这个文件,所以在这个文件打开的状态下,如果后面还有函数要对这个文件进行操作,就把这个索引值传过去作为参数。当文件关闭时这个索引值就就失去了效力,就是一个普通的值。
3.不带缓存的(系统调用)-文件访问
errno是错误原因,是由系统宏定义的。返回值是数字,每一个数字对应一个错误类型。输出errno是错误类型对应的数字。(打印它需要头文件errno.h
perror()将错误原因翻译成字符串,就是输出errno对应的错误类型。括号中若填入内容也会打印出来,一般吧相应函数名填入,这样在代码量较大,调用函数较多时如果发生错误可以准确定位到具体的函数。
-
creat函数
int creat(const char *filename, mode_t mode )const确保传入的文件名(存储地址)不可修改,mode表示创建模式,即设置文件的权限, S_IRUSR可读?S_IWUSR 可写?S_IXUSR 可执行? S_IXRWU 可读、可写、可执行
? 从man手册可以看到每一个宏都对应一个数字,所以mode中传对应数字也可以。 -
open函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);功能:两种形式,已经存在文件,两个参数,直接打开;文件不存在,三个参数,先创建再打开。
所以可以理解为兼容了上面creat函数的功能,所以creat函数用的较少。
通过man手册可以看到,creat和open是共用一个man手册的,所以creat有的功能open都有。
在打开文件时用到参数flag,这个参数是用操作系统已经宏定义好的,如O_CREAT这类形式,与mode如S_IRWXU这类形式不同。
有一个量为O_EXCL,要求文件必须不存在,必须要创建再打开,相当于是回复成了creat的功能,否则就打开失败。
flag为打开方式,与文件本身的权限无关,只是规定了在文件打开后的操作方式,在文件关闭后就失效了。 -
write函数
write(int fd, const void *buf, size_t count);功能:向文件中写入信息
参数说明 fd:写入的文件
buf:用于存放要写入文件中的信息
count:写入的长度
通俗点理解三个参数分别为:往哪儿写,写什么,写多少
返回值:成功时,返回写入的字节数(零表示未写入任何内容)。出错时返回-1,并正确设置errno(错误原因)。 -
read函数
-参数和write类似,功能为读取文件的信息。从哪儿读,读到哪儿,读多少。 -
lseek函数
lseek(int fildes, off_t offset, int whence)功能:将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置(man手册翻译)
我的理解:可以移动文件指针,也可以获取文件长度,即在文件读取完后(文件指针移动到了最后),不移动文件指针返回文件指针相对应文件头的长度
whence参数可用值是由系统宏定义的,可以用SEEK_SET:相对文件开头、SEEK_CUR:相对文件读写指针当前位置、SEEK_END:相对文件末尾
offset可以取负值,相当于向前移动。
返回值:成功完成后,lseek()返回测量的结果偏移位置
从文件开始的字节数。否则,返回值(off_t)-1
并设置errno以指示错误。 -
memset函数
void *memset(void *s, int c, size_t n);功能:函数的作用是:用常量字节c填充s指向的内存区域的前n个字节。
可以用来清空数组, memset(buf, 0, sizeof(buf)) 这种清空原理是用0覆盖数组,可以理解为将数组初始化了。
文件打开后,读取或者写入操作都有文件指针辅助控制,每读或者写一个,文件指针就向后移动一个,所以如果在调用了write函数后直接调用read函数读取,是读不出数的,因为write执行结束,文件指针已经指向了文件尾,这时再read是从文件尾开始读取,读不到任何信息。做法有两种:1、关闭文件,再打开使用read函数就能读取了,因为文件关闭后文件指针就自动回到文件首部了。2、用lseek函数,调整文件指针,即将文件指针调整到文件首部。
main函数参数使用 int main(int argc, char *argv[])
argc:向main函数传递的参数个数,实际要多一个,因为还有加上main本身的执行函数,即./main。
argv[], 保存命令行传入的参数值
失败返回-1
4.带缓存的(库函数)- 文件访问
不带缓存的I/O对是文件描述符操作,带缓存的I/O是针对流的。标准I/O库就是带缓存的I/O,它由ANSI C标准说明。不带缓存称为文件I/O。
在执行效率上来看,文件I/O低于标准I/O。文件I/O是通过系统调用使用内核函数直接对磁盘进行操作,属于跨介质操作,会增加系统的开销。标准I/O也会进行跨介质操作,但是有一个缓存区,先读写缓存区,再访问实际磁盘文件,这样就减少了跨介质操作的次数,效率就有所提高。
从通用性上看,文件I/O是调用操作系统内核函数对磁盘操作,所以只有在LINUX操作系统中才能完成,可移植性较差。标准I/O是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。即只要有函数包和头文件就能在任意操作系统上运行了。
标准I/O提供缓存的目的就是减少调用read和write的次数,它对每个I/O流自动进行缓存管理(标准I/O函数通常调用malloc来分配缓存)。它提供了三种类型的缓存:
-
全缓存:当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。
-
行缓存:当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。
-
无缓存:相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。
关于缓存,如printf,遇到换行就刷新缓冲区,将缓冲区中的记录输出到屏幕。
标准I/O的相关函数:
-
int fclose(FILE * stream)
函数说明: fclose()用来关闭先前fopen()打开的文件。此动作会让缓冲区内的数据写入文件中,并释放系统所提供的文件资源
返回值: 若关文件动作成功则返回0,有错误发生时则返回EOF并把错误代码存到errno。 -
int fputc(int c,FILE * stream);
函数说明: fputc 会将参数c 转为unsigned char 后写入参数stream 指定的文件中。
返回值: fputc()会返回写入成功的字符,即参数c。
若返回EOF则代表写入失败。 -
int fgetc(FILE * stream);
函数说明: fgetc()从参数stream所指的文件中读取一个字符。若读到文件尾而无数据时便返回EOF。
返回值: fgetc()会返回读取到的字符,若返回EOF则表示到了文件尾。 -
int fputs(const char * s,FILE * stream);
函数说明: fputs()用来将参数s所指的字符串写入到参数stream所指的文件内。
返回值: 若成功则返回写出的字符个数,返回EOF则表示有错误发生。 -
char * fgets(char * s,int size,FILE * stream);
函数说明: fgets()用来从参数stream所指的文件内读入字符并存到参数s所指的内存空间,直到出现换行字符、读到文件尾或是已读了size-1个字符为止,最后会加上NULL作为字符串结束。
返回值: fgets()若成功则返回s指针,返回NULL则表示有错误发生。 -
size_t fread(void * ptr,size_t size,
size_t nmemb, FILE * stream)
函数说明: fread()用来从文件流中读取数据。
参数:stream为已打开的文件指针,
ptr: 指向欲存放读取进来的数据空间, 读取的字符数以参数size*nmemb来决定。
返回值:返回实际读取到的nmemb数目。 -
size_t fwrite(const void * ptr, size_t size, size_t nmemb, FILE * stream)
函数说明:fwrite()用来将数据写入文件流中。
参数: stream:为已打开的文件指针,
ptr: 指向欲写入的数据地址,总共写入的字符数以 参数size*nmemb来决定。
返回值: 返回实际写入的nmemb数目。 -
long ftell(FILE * stream);
函数说明:ftell()用来取得文件流目前的读写位置。
参数stream为已打开的文件指针。
返回值:当调用成功时则返回目前的读写位置,
若有错误则返回-1,errno会存放错误代码。
错误代码:EBADF 参数stream无效或可移动读写位置的文件流。 -
int getc(FILE * stream);
函数说明: getc()用来从参数stream所指的文件中读取一个字符。若读到文件尾而无数据时便返回EOF。
注意: getc()与fgetc()作用相同,但getc()为宏定义,非真正的函数调用。
返回值: getc()会返回读取到的字符,若返回EOF则表示到了文件尾。 -
int putc(int c,FILE * stream);
函数说明:putc()会将参数c转为unsigned char后写入参数stream指定的文件中。
注意: 虽然putc()与fputc()作用相同,但putc()为宏定义,非真正的函数调用。
返回值:putc()会返回写入成功的字符,即参数c。
若返回EOF则代表写入失败。 -
int fprintf(FILE * stream, const char * format,…);
函数说明:fprintf()会根据参数format字符串来转换并格式化数据,然后将结果输出到参数stream指定的文件中,直到出现字符串结束(‘\0’)为止。
返回值:成功则返回实际输出的字符数,失败则返回-1,错误原因存于errno中。 -
Int fscanf(FILE * stream ,const char *format,…)
函数说明: fscanf()会自参数stream的文件流中读取字符串,再根据参数format字符串来转换并格式化数据。
返回值:成功则返回参数数目,失败则返回-1,错误原因存于errno中。 -
int feof(FILE * stream);
函数说明: feof()用来侦测是否读取到了文件尾,参数stream为fopen()所返回之文件指针。如果已到文件尾则返回非零值,其他情况返回0。
返回值: 返回非零值代表已到达文件尾。
参考链接:linux学习—文件I/O和标准I/O