Linux系统编程

内容来源:李慧琴老师视频课程笔记整理 + 《Unix高级系统编程》读书笔记(记录记录自己不太清楚的内容)
宗旨:一切最终不以代码展示呈现的方式都是耍流氓
3~16除9章
建议:在Linux中使用普通用户.

makefile书写(逐步添加):

CFLAGS+=-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -Wall

I/O

标准I/O:
Windows:
二进制流:换行符 《----》 ‘\n’
文本流:换行符 《----》‘\r’''\n'
Linux
换行符: 《----》 '\n'
3,5,14
intput&output是一切实现的基础

标准IO----stdio

—读写一个字符(返回EOF)
按字符输入:fgetc() ,getc(),getchar()
按字符输出:fputc() ,putc(),putchar()
—读写一行(返回NULL)
按行输出:puts(),fputs()
按行输入:gets() ,fgets() 到文件末尾返回NULL,遇到’\n’或者输入size-1个字符返回,总是包含’\n’
----读写若干对象:
fread(),fwrite()
fgetc读写效率低,fgets:可以使用buf[strlen(buf)-1]来判断读写一行

  1. 依赖于系统IO,屏蔽sysio的差异
  2. FILE类型贯穿始终.-----是一个结构
typedef struct iobuf{
int cnt; /*剩余的字节数*/
char *ptr; /*下一个字符的位置*/
char *base; /*缓冲区的位置*/
int flag; /*文件访问模式*/
int fd; /*文件描述符*/
}FILE;
  1. fopen()
FILE *fopen(const char *path, const char *mode);
mode:
       r      打开文本文件,用于读。流被定位于文件的开始(第一个有效字节)。
       r+     打开文本文件,用于读写。流被定位于文件的开始。
       ----r必须存在
       w      将文件长度截断为零,或者创建文本文件,用于写。流被定位于文件的开始。
       w+     打开文件,用于读写。如果文件不存在就创建它,否则将截断它。流被定位于文件的开始。
       a      打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。流被定位于文件的末尾。
       a+     打开文件,用于追加              
       (在文件尾写)。如果文件不存在就创建它。读文件的初始位置是文件的开始,但是输出总是被追加到文件的末尾。
注意区分最后一个字节(当前读的位置)和文件尾写(最后一个有效字节的下一个字符)
加b和不加b:Linux只有流的概念,Windows下有流和文本的概念
\\error:返回NULL
注:errno 是记录系统的最后一次错误代码

Linux中error的各种宏所在的位置/usr/include/asm-generic

作用
errno 是 error number 的缩写,意味系统调用错误码。
如果系统调用返回成功,errno 有可能但不一定会置0;而系统调用出错时,errno 必定会被设为对应的错误编号。因此,强迫症患者可以在调用系统调用之前,手动将 errno 置0。不过,如果系统调用返回成功,谁会闲着没事去看 errno 呢?
相关函数
一般来说,我们是不是需要了解每个 errno 的编号是什么意思的。我们会使用两个C库函数帮我们翻译 errno 的意思就可以了。
他们分别是:
#include <string.h>
char *strerror(int errnum);                  //将错误码以字符串的信息显示出来
#include <stdio.h>
void perror(const char *msg);           //打印系统错误信息
 perror 封装了错误码errno,将error表示的错误信息重定向到stderr上,其输出格式为:message:errno表示的错误信息.'


多线程
在没有多线程以前,errno 其实就是一个全局变量。这是很好理解的,不过已经是过去式了。
因为每个线程都有可能会调用系统调用,那么后来的错误,就会把前面的错误覆盖掉。
如果我们不能保证 errno 的可靠性,那 errno 还有什么意义?
所以,它变了。
在多线程编程时,需要包含头文件 #include <errno.h>
errno 不再是一个全局变量了,现在它已经变成了一个宏
虽然 errno 变了,但它依然是一个左值(左值就是可以写成 “左值 = 右值” 的形式)。
extern int *__errno_location(void);
#define errno (*__errno_location())
首先,声明了来自外部的函数 __errno_location();
注意哦,这个函数的返回值是一个函数指针,这个指针指向线程自己的 errno 的位置
,通过对这个指针解引用,就可以访问线程唯一的 errno。

所以,errno 被定义为 (*__errno_location())。
原文链接:https://blog.csdn.net/tissar/article/details/87996113

errno的演示案例:-----主要演示:perror(char *),strerror(int ):

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	FILE *fp;
	
	fp = fopen("temp","r");
	if(fp == NULL)
	{
	    fprintf(stdout,"fsiled info = %s\n",strerror(errno));
	    fprintf(stdout,"——————————————————————————————");
	    perror("fopen(): -------------------"); //
	    fprintf(stdout,"——————————————————————————————");
    	fprintf(stdout,"fsiled info = %s\n",strerror(errno));

	    fprintf(stderr,"fopen() failed! errno=%d\n",errno);
	    fprintf(stdout,"fsiled info = %s\n",strerror(errno));
	    exit(1);
	}
	puts("ok");
	exit(0);
}


/*
fsiled info = No such file or directory
__________________________
fopen():---------- : No such file or directory
__________________________
fopen() failed! errno=2
fsiled info = No such file or directory

*/

fopen()返回的是FILE*:位于堆上
4. fclose()
fclose(fp);
最多打开文件的数目案例

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	FILE *fp;
	int count = 0;
	while(1)
	{
		fp = fopen("tmp","r");
		if(fp == NULL)
		{
		    perror("fopen():---------- ");
		    break;
		}
		count++;
	}
	printf("max = %d\n",count);
	exit(0);
}
/*
fopen():---------- : Too many open files
max = 1021
*/

分析:一个文件默认打开stdin,,stdout,stderror三个,加上1021所以打开的文件的做多个数是1024
一个命令:ulimit:用来限制每个用户可使用的资源 ,所以一个文件可以打开的文件的个数可以修改

yangpipi@yangcentos7 io]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7800
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

  1. fgetc()
    int getchar(void); getchar() is equivalent to getc(stdin).
    int getc(FILE *stream); getc() is equivalent to fgetc()
    int fgetc(FILE *stream);
    fgetc和getc的差异:
    getc() ---- 是一个宏,所以可能会发生有副作用的表达式
    fgetc()----是一个函数,建议使用
    内核的实现大多数使用的是宏来实现----为了快,不占用运行时间,只占用编译时间.
    而函数的实现—只占用运行时间不占用编译时间.
  2. fputc():
    int putchar(int c);putchar©; is equivalent to putc(c,stdout).
    int putc(int c, FILE *stream);putc() is equivalent to fputc()
    int fputc(int c, FILE *stream);
    putc和fputc差异同上.
    案例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//注意argc是传入的参数的个数(包含函数名),argv[0]是函数名
int main(int argc,int ** argv)
{
	FILE * fw,* fr;  //小心FILE * fw,fr
	int text;  //不要定义为char因为char是未定义的有无符号

	fr = fopen(argv[1],"r");
	if(NULL == fr)  //以后所有判断形式参照括号,这个把我搞残了
	{
	    perror("fopen(r)");
	    exit(1);
	}
	fw = fopen(argv[2],"w");
	if(NULL == fw)
	{
	    perror("fopen(w)");
	    exit(1);
	}
	while(1)
	{
	    text = fgetc(fr);
	    if(text == EOF)
	    {
	      printf("copy finish!\n");
	      break;
	    }
	    fputc(text,fw);
	
	}	

	fclose(fr);
	fclose(fw);
	exit(0);
}

上面的案例输出警告:

mycopy.c: 在函数‘main’中:
mycopy.c:11:2: 警告:传递‘fopen’的第 1 个参数时在不兼容的指针类型间转换 [默认启用]
  fr = fopen(argv[1],"r");
  ^
In file included from mycopy.c:1:0:
/usr/include/stdio.h:272:14: 附注:需要类型‘const char * __restrict__’,但实参的类型为‘int *extern FILE *fopen (const char *__restrict __filename,
              ^
mycopy.c:17:2: 警告:传递‘fopen’的第 1 个参数时在不兼容的指针类型间转换 [默认启用]
  fw = fopen(argv[2],"w");
  ^
In file included from mycopy.c:1:0:
/usr/include/stdio.h:272:14: 附注:需要类型‘const char * __restrict__’,但实参的类型为‘int *extern FILE *fopen (const char *__restrict __filename,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>


int main(int argc,int **argv)
{
	FILE *fp = NULL;
	int count = 0;
	if(2>argc)
	{
	   perror("permeter is enough!");
	   exit(1);
	}
	
	fp = fopen(argv[1],"r");
	while(EOF!=fgetc(fp))
	{
	    count++;
	   
	}
	printf("letter is %d\n",count);
	exit(0);
}
  1. fgets
    char *gets(char *s); //这里面没有设置输入字符的个数,----不安全
    char *fgets(char *s, int size, FILE *stream);
    fgets结束时机:
    a. size-1
    b.‘\n’
    例如:
    abcd
#define SIZE 5
fgets()

读取上面的字符两次;
第一次:
a b c d '\0'
第二次:
'\n' '\0'
9. fputs()
int puts(const char *s);
int fputs(const char *s, FILE *stream);
10. fwrite()
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
11.fread()
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
想读的字节个数:size*nmemb
对于fread和fwrite1建议只是用单字节操作:
原因:

1=>当要读的文件只有5个字节时

fread(buf,1,10,fp)(每次读取1个字节,读取十次)
>返回5读取5个字节
>fread(buf,10,1,fp)(每次读取10个字节,读取一次)
>返回0读取??字节
  1. sprintf()
#include<stdio.h>
#include<stdlib.h>

int main()
{
	char buf[100];
	sprintf(buf,"%d-%d-%d",2021,5,13);
	printf("%s\n",buf);
	exit(0);
}
  1. fscanf()
    int sprintf(char *str, const char *format, ...); int fprintf(FILE *stream, const char *format, …);`
  2. int atoi(const char *nptr);

文件位置指针
14. void rewind(FILE *stream); ==== fseek(fp,0L,SEEK_SET);将文件指针放到文件首.
15. fseek()
int fseek(FILE *stream, long offset, int whence);
whenceca参数:SEEK_SET, SEEK_CUR, or SEEK_END
具体应用可实现空洞文件:一个文件的大小为4K,此时使用lseek函数将文件的头移动到地址6k处,再用write函数将文件写入,这样的导致4000-6000之间产生了空洞,因为这一部分并没有写入任何的数据,这一部分区域就称为文件的空洞,此文文件就叫做空洞文件

//使用fseek和ftell来实现文件大小的测量.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>


int main(int argc,int **argv)
{
	FILE *fp = NULL;
	long count = 0;
	if(2>argc)
	{
	   perror("permeter is enough!");
	   exit(1);
	}
	
	fp = fopen(argv[1],"r");
	fseek(fp,0,SEEK_END);
	count = ftell(fp);
	printf("letter is %ld\n",count);
	exit(0);
}

函数设置文件指针stream的位置
16. ftell()
long ftell(FILE *stream);
ftell函数用来返回当前文件指针的位置。定义在stdio.h头文件中。
由于受制于long类型大小,导致字节数收到限制.,上面函数的替换:(但是上面的使用与C89和C99移植性高这只适用于POXIC标准)

int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);
但是注意On many architectures both off_t 
and long are 32-bit types, but compilation with
#define _FILE_OFFSET_BITS 64
will turn off_t into a 64-bit type.

每次编译应该这样:gcc a.c -o a -D _FILE_OFFSET_BITS=64 或者使用makefile文件
vim makefile
CFLAGES+=-D_FILE_OFFSET_BITS =64
注意:D选项是用来在使用gcc/g++编译的时候定义宏的

[yangpipi@yangcentos7 linux_c]$ make a
cc -D_FILE_OFFSET_BITS=64    a.c   -o a

  1. fflush()
    int fflush(FILE *stream);
#include<stdio.h>
#include<stdlib.h>
int main()
{
        int i;
        printf("Before while()");//打印不出来;
        while(1)printf("After while()");
        exit(0);
}

fflush
为NULL:刷新所有打开的流;
stdout:

全缓存(默认形式只要不是终端设备)

要求填满整个缓存区后才进行I/O 系统调用操作。对于磁盘文件通常
使用全缓存访问。 可以调用ffresh进行刷新缓存.

行缓存

涉及一个终端时(例如标准输入和标准输出),使用行缓存。
行缓存满自动输出
碰到换行符自动输出

/n:刷新缓冲区

setvbuf可以设置缓存类型:函数 setvbuf 可以用在任何打开的流上,改变它的缓冲。参数 mode 必须是下列三个宏之一:

int main()
{
        printf("[%s:%d]:before:while()\n",__FUNCTION__,__LINE__);
    	//上面如果没有/n,则什么也输不出。
        while(1);     //放置sleep(2);也会出现这种情况。
        printf("[%s:%d]:after:while()",__FUNCTION__,__LINE__);
}  
行缓冲区刷新的条件:

printf()函数的缓冲区大小为1024个字节,当超出缓冲区大小时缓冲区就会被刷新。
1.进程结束。
2.遇到\n。
3.缓冲区满。
4.手动刷新缓冲区fflush(stdout)。
5.调用exit(0);但是还可以调用_exit(0),不刷新缓冲区。

无缓存

标准错误流stderr 通常是不带缓存区的,这使得错误信息能够尽快地
显示出来。
无论声明缓存在程序执行之后都会输出

int main()
{
        printf("[%s:%d]:before:while()\n",__FUNCTION__,__LINE__);
    	//上面如果没有/n,则什么也输不出。
        while(1);     //放置sleep(2);也会出现这种情况。
        printf("[%s:%d]:after:while()",__FUNCTION__,__LINE__);
}  
行缓冲区刷新的条件:

printf()函数的缓冲区大小为1024个字节,当超出缓冲区大小时缓冲区就会被刷新。
1.进程结束。
2.遇到\n。
3.缓冲区满。
4.手动刷新缓冲区fflush(stdout)。
5.调用exit(0);但是还可以调用_exit(0),不刷新缓冲区。

20.getline
ssize_t getline(char **lineptr, size_t *n, FILE *stream);

#define _GNU_SOURCE
       #include <stdio.h>
       #include <stdlib.h>

所以在makefile文件中添加:-D_GNU_SOURCE
CFLAGS+=-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE
案例:

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


int main(int argc,char **argv)
{
        FILE *fp = NULL;
        size_t linesize;
        char *linebuf = NULL;
        if(argc < 2)
        {
           fprintf(stderr,"%s:参数不够\n",argv[0]);
           exit(1);
        }

        fp = fopen(argv[1],"r");
        if(fp==NULL)
        {
          perror("fp is NULL");
          exit(1);
        }
        puts("参数无误|\n");
        while(1)
        {       puts("进入循环\n");
            if(getline(&linebuf,&linesize,fp)<0)
                break;
            printf("%s\n",linebuf);
            printf("%d\n",linesize);

        }
        exit(0);
}

提示:按照上面的程序会报段错误,只要将linesize初始化为0即可

在gcc编译器中,对标准库进行了扩展,加入了一个getline函数。该函数的定义以下:
#include ssize t getline(char**lineptr,size tn,FILEstream);其中1ineptr指向一个动态分配的内存区域。机是所分配内存的长度。
假如
1ineptr是NULL的话,getline函数会自动进行动态内存的分配忽略
机的大小,因此使用这个函数很注意的就使用要注意自己进行内存的释放。
假如1ineptr分配了内存,但在使用过程中发觉所分配的内存不足的话,getline函数会调用realloc函数来重新进行内存的分配,同时更新】ineptr和机。
注意
1ineptr指向的是一个动态分配的内存,由malloc,calloc或realloc分配的,不能是静态分配的数组。


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


int main(int argc,char **argv)
{
        FILE *fp = NULL;
        size_t linesize;
        char *linebuf = NULL;
        if(argc < 2)
        {
           fprintf(stderr,"%s:参数不够\n",argv[0]);
           exit(1);
        }

        fp = fopen(argv[1],"r");
        if(fp==NULL)
        {
          perror("fp is NULL");
          exit(1);
        }
        puts("参数无误|\n");
        while(1)
        {       puts("进入循环\n");
            if(getline(&linebuf,&linesize,fp)<0)
                break;
            printf("%s\n",linebuf);
            printf("%d\n",linesize);

        }
        //free(linebuf); 不建议这样,如果实现使用new就完了
        fclose(fp);
        exit(0);
}

所以自己实现getline

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// #include<mcheck.h>

// void cleanup(char** pointer) {
//   free(*pointer);
//   *pointer = NULL;
// }

// 自行实现getline函数
ssize_t mygetline(char** line, size_t *n, FILE *fp)
{
    char *buf = *line;
    //c来存储字符,i来记录字符串长度
    ssize_t c =0;
    ssize_t i =0;

    //buf为空或n为0时动态分配空间
    if(buf == NULL || *n==0)
    {
        *line = malloc(10);
        buf = *line;
        *n = 10;
    }

    while((c = fgetc(fp)) != '\n')
    {
        if(c == EOF)
            return -1;
        //留2个空间给'\n'和'\0'
        if(i < *n - 2)
            *(buf + i++) = c;
        else
        {
            //空间不足,需要扩展空间,重新进行分配
            *n = *n + 10;
            buf = realloc(buf, *n);
            *(buf + i++) = c;
        }
    }

    *(buf + i++)='\n';
    *(buf + i)='\0';
    return i;
}

int main(int argc, char **argv)
{
    
    // mtrace();
    FILE *fp;
    char *linebuf = NULL;
    size_t linesize = 0;

    if(argc < 2)
    {   
        fprintf(stderr, "Usage:%s <src_file>\n", argv[0]);
        exit(1);
    }

    fp = fopen(argv[1], "r");
    if(fp == NULL)
    {
        perror("fopen()");
        exit(1);
    }

    
    while(1)
    {
        if(mygetline(&linebuf, &linesize, fp) < 0) // if(getline(&linebuf, &linesize, fp) < 0)
            break;
            printf("%lu\n", strlen(linebuf));
            printf("%lu\n", linesize);
    }

    fclose(fp);
    free(linebuf);
    exit(0);
}

EOF是一个计算机术语,为End Of File的缩写,在操作系统中表示资料源无更多的资料可读取。资料源通常称为档案或串流。通常在文本的最后存在此字符表示资料结束

临时文件

  1. char *tmpnam(char *s);
  2. FILE *tmpfile(void);
临时文件关键点如下:
1.如何保证不冲突。
2.及时销毁 :造成内存吃紧,也会导致问题1增大冲突的可能性。
功能:create a name for a temporary file创建一个临时文件
char *tmpnam(char *s);
返回值:返回一个指向临时文件的指针
缺陷:不是原子操作(产生名字,再创建文件)
建议不使用这个函数:bugs:ever use this function.  Use mkstemp(3) or tmpfile(3) instead.
功能:tmpfile - create a temporary file创建一个临时文件
FILE * tmpfile(void);
description:以二进制的读写方式打开一个临时文件,当文件关闭后自动删除---匿名文件(没名字:ls命令看不到)
返回值:返回一个文件指针
引用:http://t.zoukankan.com/muzihuan-p-5262538.html

系统调用IO(文件IO)—sysio

  1. 不同的系统提供的sysio不一样
  2. 每打开一个文件都会创建一个结构体,这个结构体的指针会放在一个数组中,系统会将对应的下标返回给用户即描输字 所以之前讲到的ulimit -a可以修改打开一个文件的最多个数,实质上实在修改数组的大小
  3. 注意上面提到的数组是存在在一个进程空间中的,一个进程一个这样的数组.
    请添加图片描述
    注意:每个结构体内部都有一个计数器,统计有一个指针指向他(也就是上面的数组中的几个元素指向他,所以当数组中同时有两个元素指向同一个结构体时,free掉其中的一个元素,只是将指向的结构体中的计数器减一.)
  4. 三个标准流:stdout,stdin,stderr
  5. 文件描述符与流的区别:
 任何一种操作系统中,程序在开始读写一个文件的内容之前,必须首先在程序与文件之间建立连接或通信通道,这一过程称为打开文件。打开一个文件的目的可以是为了读或者为了写,也可以是即读又写。
    UNIX系统中有两种机制用于描述程序与文件的这种连接:
    (1)文件描述符
    (2)流
文件描述符 和 流 相同点:
6. 都是用来表示用户程序与被操作的文件之间的连接,并在此连接的 基础上对文件进行读写等访问。
7. 都能表示与普通文件,与设备(如终端),与管道或者套接字的连接,用户打开一个文件,要么返回文件描述符,要么返回一个流。
8.  都包含了一大类的I/O库函数
文件描述符 和 流 不同点:
文件描述符表示为int类型的对象。例如标准输入对应文件描述符0,标准输出对应文件描述符1。
而流则表示为指向结构FILE的指针FILE* ,因此流也称为“文件指针”
如果需要对特定设备进行控制操作,必须使用文件描述符方式,没有函数能对流进行这类操作。
如果需要按照特殊的方式进行I/O操作(例如非阻塞的方式),必须使用文件描述符方式,也没有函数能对流进行这类操作。

文件描述符 和 流的关系:
   流给用户程序提供了更高一级的(功能更强大,使用更简化)的I/O接口,它处在文件描述符方式的上层,也就是说,流函数是通过文件描述符函数来实现的。
流 相对于 文件描述符的优点:
执行实际输入输出操作的流函数集合比文件描述符函数要丰富很多,而功能也灵活,强大不少。 
文件描述符函数只提供简单的传送字符块的函数
流函数提供格式化I/O,字符I/O,面向行的I/O等大量函数
流函数有利于程序的移植,任何基于ANSI C的系统都支持流,文件描述符的支持则较弱
————————————————
版权声明:本文为CSDN博主「shangtang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shangtang1/article/details/78665018
  1. 文件描述符有限使用最小的而且文件描述符是整数

文件IO操作:open,close,read,write,lseek

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);
返回: 若成功为文件描述符,若出错为-1,为什么返回-1,因为数组下标没有负数.
功能: 打开或创建一个文件
pathname:要打开或者创建的文件路径
flags:用来说明此函数的多个选择项
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
mode:新建文件的访问权限,对于open函数而言,仅当创建新文件
时才使用第三个参数。

上面的头文件加sys的原因:如果头文件在/usr/include下的直接#include<头文件名>即可,但是在/usr/include下的目录下的就要深入目录下了#incldue<sys/types.h>

用下列一个或多个常数进行或运算构成flags参数(这些
常数定义在<fcntl.h>头文件中)
O_RDONLY 以只读方式打开文件                            		******************
O_WRONLY 以只写方式打开文件                                    ******************
O_RDWR 以读写方式打开文件                             			******************
O_APPEND 以追加模式打开文件,每次写时都加到文件的尾端,      		******************
但在网络文件系统进行操作时却没有保证。
O_CREAT 如果指定的文件不存在,则按照mode参数指定的文件
权限来创建文件。                           					 ******************
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错
。这可测试一个文件是否存在。但在网络文件系统进行操作时却没
有保证。      												  ******************
O_DIRECTORY 如果参数pathname不是一个目录,则open出错
O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将         		  ******************
其长度截短为0。清空文件.
O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件
或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续
的I/O操作设置非阻塞方式。可见只能对特定类型的文件设置非阻塞功能,并不是所以文件

与标注IO读写操作的映射:

O_RDONLY       					 ===== r
O_WRONLY 						 ===== r+
O_WRONLY|O_CREAT|O_TRUNC 		 ===== w
O_RDWR|O_CREAT|O_TRUNC 			 ===== w+
close()

关闭文件

#include <unistd.h>
int close(int fd);
返回: 若成功为0,若出错为-1
功能: 关闭一个打开的文件
参数
fd:已打开文件的文件描述符
当一个进程终止时,它所有的打开文件都由内核自动
关闭。
read()

读取文件

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回: 读到的字节数,若已到文件尾为0,若出错为-1
功能: 从打开文件中读数据
参数
fd:读取文件的文件描述符
buf:存放读取数据的缓存
count:要求读取一次数据的字节数
有多种情况可使实际读到的字节数少于要求读字节数
读普通文件时,在读到要求字节数之前已到达了文件尾端。
当从终端设备读时,通常一次最多读一行。
当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
某些面向记录的设备,例如磁带,一次最多返回一个记录。
进程由于信号造成中断
读操作从文件的当前位移量处开始,在成功返回之前,该位移量
增加实际读得的字节数。
write()

写入文件

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
返回: 若成功为已写的字节数,若出错为-1   返回为0时不一定是出错了
功能: 向打开的文件中写数据
参数
fd:写入文件的文件描述符
buf:存放待写数据的缓存
count :要求写入一次数据的字节数
其返回值通常与参数count的值不同,否则表示出错
write出错的一个常见原因是:磁盘已写满,或者超过了对一个给
定进程的文件长度限制。
对于普通文件,写操作从文件的当前位移量处开始。如果在打开
该文件时,指定了O_APPEND选择项,则在每次写操作之前,将
文件位移量设置在文件的当前结尾处。在一次成功写之后,该文
件位移量增加实际写的字节数。

案例:

#include <unistd.h>
#define BUFFER_LEN 1024
void copy(int fd1, int fd2)
{
        char buffer[BUFFER_LEN];
        ssize_t nreads;
        fd1 = open(,O_RDONLY);
        fd2 = open(,O_WRONLY|O_CREAT|O_TRUNC,0600);
        while((nreads = read(fd1, buffer, BUFFER_LEN)) != 0){ 
                if(nreads < 0){ 
                 fprintf(stderr, "read error: %s\n", strerror(errno));
                }else{
                if(write(fd2, buffer, nreads) != nreads){
                 fprintf(stderr, "read error: %s\n", strerror(errno));
                }           
                }   
        }   

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

#define BUFSIZE 1024

int main(int argc,char **argv)
{
   
   int sfd,dfd;
   int len,ret;
   int pos;
   char buf[BUFSIZE];
   if(argc<3)
   {
     fprintf(stderr,"Usage....\n");
     exit(1);
   }

   sfd = open(argv[1],O_RDONLY);
   if(sfd<0)
   {
     perror("open()");
     exit(1);
   }

   dfd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0600);
   if(dfd < 0)
   {
     close(sfd);
     perror("dfd open()");
     exit(1);
   }

   while(1)
   {
     len = read(sfd,buf,BUFSIZE);
     if(len<0)
     {
	perror("read()");
	break;
     }
     if(len==0)
	 break;

     pos=0;
     while(len>0)
     {
    	ret = write(dfd,buf+pos,len);
    	if(ret < 0)
    	{
   	    	perror("write()");
       		exit(1);
    	}
	pos+=ret;
	len-=ret;
     }
   }

   close(dfd);
   close(sfd);
}
lseek()

文件定位

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
返回: 若成功则返回新的文件位移量(绝对偏移量)---相对文件的开头,若出错为-1
功能: 定位一个已打开的文件
//c标准中的
int fseek(FILE *stream,long offset,int whence);
fd:已打开文件的文件描述符
offset:位移量,不一定是文件开头可以是文件的任意位置.(与返回值有所差异)
whence:定位的位置
SEEK_SET:将该文件的位移量设置为距文件开始处offset个字节。
SEEK_CUR:将该文件的位移量设置为其当前值加offset,offset可为正
或负。
SEEK_END:将该文件的位移量设置为文件长度加offset,offset可为正
或负。

空洞文件:

  • 在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被设为 0。如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。
  • 用ls查看的文件大小是将空洞算在内的。
    cp命令拷贝的文件,空洞部分不拷贝,所以生成的同样文件占用磁盘空间小
    用read读取空洞部分读出的数据是0,所以如果用read和write拷贝一个有空洞的文件,那么最终得到的文件没有了空洞,空洞部分都被0给填充了,文件占用的磁盘空间就大了。不过文件大小不变。
    空洞文件作用很大,例如迅雷下载文件,在未下载完成时就已经占据了全部文件大小的空间,这时候就是空洞文件。下载时如果没有空洞文件,多线程下载时文件就都只能从一个地方写入,这就不是多线程了。如果有了空洞文件,可以从不同的地址写入,就完成了多线程的优势任务
lseek也可用来确定所涉及的文件是否可以设置位移量
。如果文件描述符引用的是一个管道或FIFO(这两种文件不支持lseek),则lseek
返回-1,并将errno设置为EPIPE。

每个打开文件都有一个与其相关联的"当前文件偏移量"
。它是一个非负整数,用以度量从文件开始处计算的
字节数。通常,读、写操作都从当前文件偏移量处开
始,并使偏移量增加所读或写的字节数。按系统默认
,当打开一个文件时,除非指定O_APPEND选择项,
否则该位移量被设置为0。
只有打开的文件,才有文件偏移量的概念,所以可知文件偏移量是不记录在文件属性中的
truncate/ftruncate

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

文件IO与系统IO的转换
  1. int fileno(FILE *stream)
  2. FILE *fdopen(int fildes, const char *mode);
练习–mycopy
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>

#define BUFSIZE 512

int main(int argc,char ** argv)
{
    int fr,fw;
    int n;
    char buf[BUFSIZE];
    char buf_read[BUFSIZE];
    fprintf(stdout,"打开的文件是:%s--%s\n",argv[1],argv[2]);
    if(argc < 2){
    
        fprintf(stdout,"%s ->参数不够:\n",argv[0]);
        exit(1);
    }

    if((fr = open(argv[1],O_RDONLY))<0){
    
        fprintf(stderr,"读文件打开失败\n");
        exit(1);
    }
    //O_RDWR,O_WRONLY,O_RDONLY
    if((fw = open(argv[2],O_RDWR|O_CREAT,O_TRUNC,0600)) < 0){
        printf("fw = %d\n",fw);
        fprintf(stderr,"写文件打开失败\n");
        close(fr);//当fw文件打开失败时那么fr已经打开了
        exit(1);
    }
    
    while((n = read(fr,buf,BUFSIZE))>0){
        write(fw,buf,n);
    }

    printf("一下内容开始读:\n");
    printf("fr = %d,fw = %d\n",lseek(fr,0,SEEK_CUR),lseek(fw,0,SEEK_CUR));
    lseek(fw,0,SEEK_SET);
    printf("fr = %d,fw = %d\n",lseek(fr,0,SEEK_CUR),lseek(fw,0,SEEK_CUR));

    while((n = read(fw,buf_read,BUFSIZE))>0){
        //write(STDOUT_FILENO,buf,n);
        fprintf(stdout,"%s ",buf_read);
    }
    printf("%d----\n",n);
    close(fw);
    close(fr);
    exit(0);
} 

文件IO和标准IO的区别

  1. 一个有缓存一个没有缓存—响应速度差异,吞吐量差异。
  2. 标准IO和系统IO不能混用.(例如写文件时,文件位置指针(pos)不是同一个)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
    putchar('a');
    write(1,"b",1);

    putchar('a');
    write(1,"b",1);

    putchar('a');
    write(1,"b",1);

    putchar('a');
    write(1,"b",1);
                                                                                                                                   
    exit(0);
}
//输出结果:bbbbaaaa

strace

  1. 监控用户进程与内核进程的交互
  2. 追踪进程的系统调用、信号传递、状态变化
    strace ./a.out
write(1, "b", 1b)                        = 1
write(1, "b", 1b)                        = 1
write(1, "b", 1b)                        = 1
write(1, "b", 1b)                        = 1
write(1, "aaaa", 4aaaa)                     = 4

IO的效率问题

#time 可以查看执行时间
[yangpipi@yangcentos7 sysIO]$ time ./stdio_complex_sysctlio 
bbbbaaaa
real	0m0.003s
user	0m0.000s
sys	0m0.003s

文件共享

原子操作

作用:解决竞争和冲突
tmpname不原子

程序中的重定向:dup(),dup2()

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
返回: 成功返回新文件描述符,出错返回-1
功能: 文件描述符的复制
就是把oldfd的指向复制一份到newfd(原来newfd指向的文件被关闭),如此一来,
再向newfd写输入就如同向oldfd写输入一样了。
参数
oldfd:原先的文件描述符
newfd:新的文件描述符
由dup返回的新文件描述符一定是当前可用文件描述符
中的最小数值。
用dup2则可以用newfd参数指定新描述符的数值。如
果newfd已经打开,则先将其关闭。如若oldfd等于
newfd,则dup2返回newfd,而不关闭它。
在进程间通信时可用来改变进程的标准输入和标准输
出设备

同步:sync(),fsync(),fdataasync()

fcntl()

案例引入:

//将标准输出的内容输出其他地方
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

#define FNAME "/tmp/out"                                                                                                            
int main()
{


    int fd;
    close(1);//关闭标准输出

    if((fd=open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600))<0){

        perror("文件打开失败:\n");
        exit(1);
    }

    puts("hello");
}

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

#define FNAME "/tmp/out"
int main()
{


    int fd;


    if((fd=open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600))<0){

        perror("文件打开失败:\n");
        exit(1);
    }
    close(1);
    dup(fd);//将fd复制到当前可用最小的文件描述符下即1下
    close(fd);                                                                                                                      
    puts("hello");
}
/**
存在的问题:
1. 源文件和目标文件时同一个文件。
2. 在并发下,有一个兄弟进程正在使用该文件但是你却关闭了他

本质是
    close(1);
    dup(fd);//将fd复制到当前可用最小的文件描述符下即1下
    不原子
    那么dup2解决了上面的问题
/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

#define FNAME "/tmp/out"
int main()
{
    int fd;
    if((fd=open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600))<0){

        perror("文件打开失败:\n");
        exit(1);
    }
  	dup2(fd,1)
  	if(fd!=1)
    	close(fd);                                                                                                                      
    puts("hello");
    //记住最后还原标准输出
}

//将标准输出重定向到文件,并且向该文件写入内容,然后恢复标准输出
int main()
{
    int fd,tmpfd,savefd;
    if((fd=open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600))<0){
        perror("文件打开失败:\n");
        exit(1);
    }
    dup2(STDOUT_FILENO,savefd);//  /*   保存标准输出   */    
    printf("savefd:%d\n",savefd);
    dup2(fd,STDOUT_FILENO);
    if(fd!=1){
        close(fd);
    }
    puts("11111111yyyyy11111111111\n");
    dup2(savefd,STDOUT_FILENO);  /*   恢复stdout   */   
    printf("我已经恢复了\n");
    exit(0);
}                      

ioctl()

sync

void sync(void);

  1. 强制从页缓存区写入到磁盘
  2. 当用户从磁盘中读取数据时,并不直接去磁盘中寻找,而是先到页缓存区找有没有,如果找到了就读取缓存区的数据,没有才去磁盘中找,写也是一个道理,写入的数据不会直接存储到磁盘中,先经过页缓存,一段时间时候才会从页缓存区存储到磁盘。
fsync

int fsync(int fd);
3. 同步内存中所有已修改的文件数据到储存设备。

fdatasync

int fdatasync(int fd);
4. fdatasync 函数类似于 fsync, 但它只影响到文件的数据。而 fsync 会更新文件的数据和属性(比如说文件的更新时间等文件属性)。
5. 根据这三个函数的特征,一般来说当你的应用无需延迟 异步,数据的实时性要求没那么高,最终只要数据一致性就可以时,调用 sync 就可以来。而 当对数据的实时性要求比较高时,可采用 fsync 同步更新数据。

fcntl

int fcntl(int fd, int cmd, ... /* arg */ );

  1. 文件描述符所变的魔术几乎都来源于该函数
ioctl

int ioctl(int d, int request, ...);
设备相关的内容

/dev/fd/目录

1.虚目录,显示的是当前进程的文件描述符信息.

//如下图所示显示的就是ls的文件描述符信息
[yangpipi@yangcentos7 sysIO]$ ls -l  /dev/fd/
总用量 0
lrwx------. 1 yangpipi yangpipi 64 620 23:55 0 -> /dev/pts/1
lrwx------. 1 yangpipi yangpipi 64 620 23:55 1 -> /dev/pts/1
lrwx------. 1 yangpipi yangpipi 64 620 23:55 2 -> /dev/pts/1
lr-x------. 1 yangpipi yangpipi 64 620 23:55 3 -> /proc/68723/fd

请添加图片描述
请添加图片描述

文件系统

4,6,7
《深入理解计算机系统》
类ls的实现,myls

1. 目录和文件

请添加图片描述
请添加图片描述

文件类型

Linux中的七种文件和七种宏
普通文件(regular file) S_ISREG()
目录文件(directory file) S_ISDIR()
块特殊文件(block special file) S_ISBLK()
字符特殊文件(chatacter special file) S_ISCHR()
FIFO(named pipe) S_ISFIFO()
套接字(socket) S_ISSOCK()
符号链接(symbolic link) S_ISLNK()

文件权限

9种文件访问权限位
用户权限
S_IRUSR, S_IWUSR, S_IXUSR
组权限
S_IRGRP, S_IWGRP, S_IXGRP
其它权限
S_IROTH, S_IWOTH, S_IXOTH
文件权限通过按位或方式构造

获取文件属性(stat,fstat,lstat)

、、ls filename命令就是读取这里的信息
struct stat{
mode_t st_mode; /*file type & permissionq权限信息*/
ino_t st_ino; /*i-node number  `ls -i看出的`*/
dev_t st_dev; /*device number (file system) 文件保存的设备*/
dev_t st_rdev; /*device number for special files*/
nlink_t st_nlink; /*number of links硬链接数*/
uid_t st_uid; /*user ID of owner*/
gid_t st_gid; /*group ID of owner*/
off_t st_size; /*size in bytes*/
time_t st_atime; /*time of last access*/
time_t st_mtime; /*time of last modification*/
time_t st_ctime; /*time of last file status change*/
blksize_t st_blksize; /*best I/O block size*/
blkcnt_t st_blocks; /*number of disk blocks allocated*/
};
获取一个文件的大小:

根据目前所学有三种方式

  1. 使用read进行多操作,当读完时,看字节数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>

#define BUFFSIZE 512
int main(int argc, char ** argv)
{
    int fr = 0;
    int size_file = 0;
    int size_per;
    int buff[BUFFSIZE];
    if(argc < 2){
        fprintf(stderr,"传入的参数不够,,,\n");
        exit(1);
    }

    if((fr=open(argv[1],O_RDONLY))<0){
        fprintf(stderr,"不能打开文件: %s\n",strerror(errno));
        exit(1);
    }

    while((size_per=read(fr,buff,BUFFSIZE))>0){
        size_file+=size_per;
        printf("%d---\n",size_per);
    }
   
    fprintf(stdout,"%s 大小为 %d\n",argv[1],size_file);
    exit(0);
}
  1. 使用lseek,直接定位到末尾.
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>

int main(int argc,char ** argv)
{
    int fr;
    int pos;
    if(argc<2){
        fprintf(stderr,"参数不够,%s",strerror(errno));
        exit(1);
    }


    if((fr=open(argv[1],O_RDONLY)) < 0){
        fprintf(stderr,"文件打开失败:%s\n",strerror(errno));
        exit(1);
    }

    lseek(fr,0,SEEK_CUR);
    pos = lseek(fr,0,SEEK_END);
    fprintf(stdout,"文件大小为: %d\n",pos);

    exit(0);
}      
  1. 直接读取文件的stat中的st-size 由于off_t会变化所以应该使用makefile -D_FILE_OFFET_BITSET=64 `
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>

static int flen(const char *fname)
{   // static修饰函数表示禁止外部扩展
    int fr;
    struct stat statres;
    
    if(stat(fname,&statres) < 0){
        perror("stat()\n");
        exit(1);
    }

    return statres.st_size;

}


int main(int argc,char ** argv)
{

    if(argc < 2){
        fprintf(stderr,"参数不够~~~");
        exit(1);
    }

    printf("文件大小为:%d\n",flen(argv[1]));
    exit(0);
}                                                                                                                                  
      
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
返回: 若成功则为0,若出错则为-1
功能: 返回一个与pathname或fd指定的文件属性信息,存储在结构体buf中
参数
pathname:文件路径名字
buf:struct stat结构体指针
lstat函数类似于stat,但是当命名的文件是一个符号连
接时,lstat返回该符号连接的有关信息,而不是由该
符号连接引用的文件的信息
  1. 文件访问权限
    都在st_mode中,它是一个16位的位图
  2. umask
    防止产生权限过松的文件.
  3. 文件权限的更改和管理
    chmod();
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
  1. 粘住位
    t位
    如果在一个目录上出现“t”位,这就意味着该目录中的文件只有其属主才可以删除

  2. 文件系统:FAT,UFS

  3. 硬链接和符号链接
    link()-------------ln
    unlink()
    remove()-----------rm
    rename() ------mv

硬链接与目录项是同义词,且建立硬链接有限制:不能给分区建立,不能给目录建立。符号链接优点:可跨分区,可以给目录建立
  1. utime
    更改最后修改的时间
  2. 目录的创建和销毁
    mkdir()
    rmdir()(empty file)
  3. 更改当前目录
    chdir()--------------cd\getcwd()-------pwd
  4. 分析目录/读取目录内容

2.系统数据文件和信息

3.进程环境

4.

并发

多进程并发(信号)

10

多线程编程

10,11

IPC

8
13
15,16

进程间的通信

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值