I/O和进线程知识总结(嵌入式系列学习5)

        嵌入式学习系列来到I/O和进程部分,本篇主要讲述了在Linux下,I/O和进线程之间的知识总结,分析了标准I/O文件I/O区别,重点讲解文件描述符的含义和具体文件的I/O编程接口;解释了库的制作,包括动态库静态库制作与使用,以及两者区别;讲解了进程线程的区别和联系、如何创建多进程以及守护进程;对于Linux进程间通信,讲解几种常用的进程通信方法,包括管道通信信号通信共享内存消息队列等;对于Linux多线程编程,讲解了多线程编程方法及注意事项等。

        整篇是本人通过学习进行笔记记载,内容丰富,将所涉及知识集中在一篇中,方便学习,如有不足,还请指出。另外我们提供嵌入式项目毕设资料与服务,远程部署、代码讲解、全方位服务。我们有专门的毕设项目辅导群(QQ:596557438)!!!

项目源码资料下载地址:箫声商城 

易学蔚来全套毕设演示(看上哪个选那个): 易学蔚来全套毕业设计汇总 · 语雀

毕设服务真实反馈,在线观看: 客户真实反馈 · 语雀

一、概念简介

1.文件 

      在Linux中,一切皆文件

2.文件类型 

Linux的七种文件属性

(1)普通文件  -

(2)目录文件  d

(3)符号链接文件(软链接): l 

(4)字符设备文件:c

(5)块设备文件 :b

(6)管道文件:p

(7)套接字文件:s

main函数解释: 

二、IO

1.IO的分类 

1.1 文件IO和标准IO 

 对一个文件有两种不同的操作方式

1) 文件IO------(linux系统提供) 系统调用

文件IO------(linux系统提供) 系统调用:当我们的应用程序要使用一些底层的功能的时候,不应该自行访问底层,而应该向操作系统发出请求。

特点:

  1. 不带缓冲区

  2. 操作系统直接提供的函数接口

  3. 调用系统调用是很耗费资源的

  4. 操作的文件是普通文件或者设备文件(硬件)

  5. 通过文件描述符(非负的数字)操作文件

 2)标准IO------(C库提供)

C库函数:在系统调用接口之上封装的接口,一个C库函数可以封装多个系统调用函数。

作用:

  1. 增强了代码的可移植性,复用性

  2. 提高了效率。 标准IO增加了一个【缓冲机制】

  3. 操作文件一般为普通文件

  4. 通过文件流(指针)操作文件

2.标准IO

2.1 流

 :文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为“流”

为啥称结构体指针为流?
因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据(每一次的放入数据:均是需要通过文件指针来进行读写指向的文件),存在三个特点:
(1)有源头:APP
(2)有目的:缓冲区
(3)持续性:不断放入数据到缓冲区

2.2 流的分类 

 文件被打开的时候,会默认具备3个类

文本流:在流中处理的数据是以字符出现

二进制流:流中处理的是二进制序列,若流中有字符,则用一个字节的二进制ASCII码表示;若是数字,则用对应的二进制数表示,对'\n'不进行变换 

系统默认打开了3个流指针

stdin (标准输入,终端键盘进行输入)

stdout标准输出,终端打印出来)

stderr标准错误,终端打印出来,不带缓冲区(意味着每一次出错就会立即刷新缓冲区))

2.3 缓冲机制

全缓冲:缓冲区被放满,程序结束,强制刷新--->>会引起缓冲区的刷新

行缓冲:缓冲区被放满,程序结束,强制刷新,遇到换行符--->>会引起缓冲区的刷新

不带(无)缓冲:不存在缓冲区的概念---<<每一次读写都是直接输出:stderr>> 

全缓存:通过fopen函数打开的流指针,这个流指针fp的缓冲区大小是 4*1024 4Kbyte

//1.缓存区满

//2.fclose(fp)

//3.return

//4.exit

//5.fflush(fp)  

printf、stdin 、stdout是行缓存,缓冲区大小是 1024byte == 1Kbyte

//1.行缓存满了 或遇到'\n'输出条件

//2.fflush可以强制刷新 (fflush)

//3.文件关闭的时候 fclose(stdout)

//4.程序结束的时候exit return

无缓存:stderr 

#include <stdio.h>

int main(int argc, char *argv[])
{
//  printf("hello world!\n");
    printf("hello world!");
    //缓存区的大小:1024Bytes
    //printf("size=%d\n",stdout->_IO_buf_end - stdout->_IO_buf_base);
    int i ;
    for(i = 0;i < 1013;i++)
    {
        fputc(' ',stdout);
    }
    //fflush(stdout);//强制刷新标准输出流
    //fclose(stdout);//关闭标准输出流
    //return 0;
    //exit(0);
    while(1);
    return 0;
} 

每一个终端都是一个文件: pts/xxx 这个就是终端对应的文件,这个文件的名字是以数字命名的

这个文件存储在 : /dev/pts/xxx 这些文件是由linux系统自动创建。

当打开一个终端时,就会重建一个新的文件与之对应 stdin、stdout、stderr都指向的是同一个文件(终端文件)。

示例:直接向终端写入'x'

ps :查看当前终端运行的进程

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

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("/dev/pts/3", "w");
	if(NULL == fp)
	{
		perror("fopen");
		return -1;
	}

	int n = 10;
	while(1)
	{
		fputc('x', fp);

		//sleep(1);
	}

	fclose(fp);

	return 0;
}

 3.操作文件 

 [[fopen/fclsoe/fgetc/fputc/fgets/fputs]=文本流/[fread/fwrite]=二进制流]

 3.1 fopen(打开文件)

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);
   
功能:打开文件

参数:
	path: 文件路径或者打开文件的名字(包含指针类型)

	mode:
		r  : 以只读方式打开,文件必须存在
        r+ : 以读写方式打开,文件必须存在
		w  : 以写的方式打开,文件不存在,则创建,文件存在,则清空
        w+ : 以读写的方式打开,文件不存在,则创建,文件存在,则清空  
		a  : 以追加的方式打开(可写),文件不存在,则创建。
        							 文件存在,则追加在文件的末尾
		a+ : 以追加的方式打开(读写),文件不存在,则创建。
        							 文件存在,则追加在文件的末尾

返回值:   
	成功: FILE *fp   流指针
    失败: NULL 

FILE *fp = fopen("./1.txt", "w+");

FILE:系统会自动为使用的文件在内存中开辟一片空间,来存储该文件的详细信息,这个空间类型为 FILE 结构体类型,该结构体由系统设计。

FILE *:流指针,在标准IO中,每次成功打开一个文件,都会返回一个流指针,这个流指针就描述了一个文件,所有的标准IO都围绕流指针来进行。

//没写一个函数都会进行出错处理

strerror(errno); //参数就是错误码errno

perror("fopen"); //参数就是字符串

 1)示例代码:利用标椎IO函数测试当前系统最大能打开的文件个数

 同一个文件,可以存在多个流指针,与之对应。

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

int main(int argc, const char *argv[])
{
	int count = 0;

	FILE *fp;

	while(1)
	{
		fp = fopen("./1.txt", "w+");
		if(NULL == fp)
		{
			//printf("errno: %s\n", strerror(errno));
			perror("fopen");
			//return -1;
			break;
		}

		printf("%p\n", fp);

		count++;
	}

	printf("count=%d\n", count);

#if 0	
	printf("%d\n", argc);
	printf("%s\n", argv[0]);
	printf("%s\n", argv[1]);
	printf("%s\n", argv[2]);
#endif
	return 0;
}
 2)输出结果

1021+3 = 1024

stdin :标椎输入流

stdout:标准输出流

stderr:标准错误流

 3.2 fclose(关闭文件)

int fclose(FILE *fp);
功能:关闭文件
参数:
  fp:流指针

fclose(fp);

为什么要关闭一个文件

一、防止其他进程操作这个文件

二、释放结构体占用的资源

在程序结束时,系统自动回收资源(不完全),所以尽量写上fclose。

3.3 freopen(改变流指针指向)

FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:改变流指针的指向
参数:
	  path: 文件路径名
      mode: 打开方式
      stream:流指针
 1)示例代码
#include <stdio.h>

int main(int argc, char *argv[])
{ 
    freopen("./2.txt","w",stdout);
    /*原始*/
    printf("xxxxxxxxxxxx\n");
    printf("hello world!\n");
    printf("xxxxxxxxxxxx\n");
    /*修改后*/
    printf("------------\n");
    printf("hello world!\n");
    printf("xxxxxxxxxxxx\n");
    printf("------------\n");
    printf("hello world!\n");
    printf("xxxxxxxxxxxx\n");

    return 0;
} 
2)输出结果 

3.4 fgetc/fputc(按字符(字节)输入/输出)

1. fgetc(获取字符) 
int fgetc(FILE *stream);
	功能:从指定的文件流中获取一个字符
	参数:指定获取一个字符所处文件的文件流
	返回值:成功返回获取到的字符值,读取到文件末尾返回EOF(-1),操作中失败返回负数
)
2. fputc(输出字符) 
int fputc(int c, FILE*stream);
	功能:向指定的文件流中输出一个字符
	参数:
		参数1:需要输出的指定字符(字符被称为单字节的整形)
		参数2:指定输出字符到的文件对应的文件流
	返回值:
		成功返回刚写入的字符值
		失败返回EOF(-1
 1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./2.txt", "r+");
	if(NULL == fp)
	{
		perror("fopen");
		return -1;
	}

	printf("%d\n", fgetc(fp));
	printf("%c\n", fgetc(fp));
	printf("%c\n", fgetc(fp));
	printf("%c\n", fgetc(fp));
	printf("%c\n", fgetc(fp));


	fputc(121, fp);
	fputc(121, fp);
	fputc(121, fp);
	fputc(121, fp);
	fputc(121, fp);
	fputc(121, fp);
	
	fclose(fp);

	return 0;
}
 2)输出结果

 3.5 fgets/fputs(按行输入/输出)

 1. fgets(读取一行数据)
char *fgets(char *s, int size, FILE *stream);
功能:读取一行的数据
参数:
    s: 内存地址  char s[];
	size: 读取的字节数(存储内容空间的大小--可以sizeof()测得)
	stream: 流指针
返回值:
    成功:读取到字符串的首地址(存储内容空间的首地址)
    失败:NULL
 1)示例代码:文件有多少行?
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{ 
    FILE *fp = fopen("./1.txt","r");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    char s[2000] = {0};
    int count =0;
    while(1)
    {
        char *p = fgets(s,sizeof(s),fp);
        if(NULL == p)
        {
            perror("fgets");
            break;
        }
        printf("%s\n",p);
        count++;
        memset(s,0, sizeof(s));

    }
    printf("count = %d\n",count);
    fclose(fp);
    return 0;
} 
2)输出结果

2. fputs(写入一行数据) 
int fputs(const char *s, FILE *stream);
功能:写入一行的数据
参数: 
    s: 内存地址 char s[];
	stream: 流指针
返回值:
    成功:非零值
    失败:-1
1)示例代码 :写入"hello world"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./2.txt", "a+");
	if(NULL == fp)
	{
		perror("fopen");
		return -1;
	}

	char buf[100] = "hello world\n";

	int n = fputs(buf, fp);

	printf("n = %d\n", n);

	fclose(fp);

	return 0;
}
2)输出结果

3)示例代码: 从标椎输入(stdin)得到字符串 写入到 文件当中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./2.txt", "a+");
	if(NULL == fp)
	{
		perror("fopen");
		return -1;
	}

	char buf[100] = {0};

	while(1)
	{
		char *p = fgets(buf, sizeof(buf), stdin);
		if(NULL == p)
		{
			perror("fgets");
			break;
		}

		if( strncmp(buf, "quit", 4)  == 0)
		{
			break;
		}

		int	n = fputs(buf, fp);

		printf("n = %d\n", n);

		memset(buf, 0, sizeof(buf));
	}

	fclose(fp);

	return 0;
}

 

 3.6 fread(以指定大小读取文件)

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:读取数据
参数:
	 参数1:存储读取一块内容之后的空间地址
	 参数2:块的大小---》建议给1
	 参数3:块的个数---》建议给sizeof()
	 参数4:要读取的文件对应的文件流
返回值:成功代表读取的块的个数,失败返回0

 1)示例代码
#include <stdio.h>

int main(int argc, char *argv[])
{ 
    FILE *fp = fopen("./1.txt","rb");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    char buf[100] = {0};
    int n = fread(buf,4,25,fp);
    printf("n=%d\n",n);
    printf("buf:%s\n",buf);
    fclose(fp);
    return 0;
} 
 2)输出结果

 3.7 fwrite(以指定大小写入文件)

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:写入数据
参数:
    参数1:需要写入的内容的空间地址
	参数2:块的大小---》建议给1
	参数3:块的个数---》建议给sizeof()
	参数4:要写入的文件对应的文件流
	
返回值:成功代表写入的块的个数,失败返回0

fwrite()函数写入文件的内容是二进制的。
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct A{
	int m;
	float n;
	char ch;
};

int main(int argc, const char *argv[])
{
	struct A a = {10, 3.14, 'a'}; 

	printf("sizeof(a) = %d\n", sizeof(a));


	FILE *fp = fopen("./2.txt", "wb+");
	if(NULL == fp)
	{
		perror("fopen");
		return -1;
	}

	fwrite(&a, sizeof(struct A) , 1, fp);

	rewind(fp);

	struct A b;

	fread(&b, sizeof(struct A), 1, fp);

	printf("%d %f %c\n", b.m, b.n, b.ch );

	fclose(fp);

	return 0;
}
2)输出结果

 

3.8 文件流的定位(文件指针的指示位置) 

1、perror(“string”);---》可以输出出错的原因
2、feof(FILE* Stream);
	   作用:判断文件是否抵达末尾(不管是文本文件还是二进制文件,都可以判断)
	   返回值:
		          抵达文件末尾---》返回值为非零
		          未抵达文件末尾---》返回值0

3.9 练习 

1.复制文件 
 1)示例代码(copy)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	if(argc!=3)
	{
		printf("%s src_file dest_file\n", argv[0]);
		return -1;
	}

	FILE *fp1 = fopen( argv[1] ,  "r");
	if(fp1 == NULL)
	{
		perror("fopen");
		return -1;
	}
	
	FILE *fp2 = fopen( argv[2] ,  "w");
	if(fp2 == NULL)
	{
		perror("fopen");
		return -1;
	}

	char buf[1024] = {0};

	while(1)
	{
		char *p = fgets(buf, sizeof(buf), fp1);
		if(p == NULL)
		{
			break;
		}
	
		fputs(buf, fp2);
	
		memset(buf, 0 ,sizeof(buf));
	}

	printf("copy success!\n");

	fclose(fp1);
	fclose(fp2);

	return 0;
}
1)输出结果 

 

2.怪兽乐园 
typedef struct Monster{
	int ID;
	char name[50];
	char species[50];
	char type[50];
}MOS;
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Monster{
	int ID;
	char name[32];
	char species[32];
	char type[32];
}MOS;

int createMonster(int n)
{
	MOS m1[n], m2[n];

	int i;
	for(i=0; i<n; i++)
	{
		printf("请输入第%d号怪兽信息: ", i+1);
	
		scanf("%d%s%s%s", &m1[i].ID, m1[i].name, m1[i].species, m1[i].type);
	}

	FILE *fp = fopen("Monster.txt", "wb+");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}

	fwrite(m1, sizeof(MOS) , n, fp);

	rewind(fp);

	fread(m2, sizeof(MOS), n, fp);

	for(i=0; i<n; i++)
	{
		printf("%d %s %s %s\n", m2[i].ID, m2[i].name, m2[i].species, m2[i].type);
	}

	fclose(fp);
}

int main(int argc, const char *argv[])
{
	createMonster(3);

	return 0;
}
2)输出结果

3.10 fseek(定位文件指针) 

int fseek(FILE *stream, long offset, int whence);
功能:定位文件指针
参数:
      stream: 流指针
      offset: 偏移量 
			100:向后偏移100个字节
    	    -100:向前偏移100个字节
      whence: 基点 
			SEEK_SET: 文件开头
      SEEK_END: 文件末尾
      SEEK_CUR: 文件当前位置
返回值:
    成功:0
    失败:-1

定位到文件末尾: fseek(fp, 0 , SEEK_END);

定位到文件末尾的前一个字节: fseek( fp, -1, SEEK_END );

1)示例代码 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./test.txt", "w");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}

	fputc('a', fp);

	fseek(fp, 0, SEEK_END);

	fseek(fp, 100, SEEK_END);

	fputc('b', fp);

	fclose(fp);

	return 0;
}
2)输出结果

3.11 ftell(获取文件大小) 

long ftell(FILE *stream);
参数:
    stream:要定位的文件流

返回值:
    成功:当前文件指针的位置
    失败:eof

3.12 rewind(文件指针返回到文件开头) 

void rewind(FILE *stream);
功能:文件指针返回到文件开头

rewind()等价于(void)fseek(stream,OL,SEEK_SET)
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./1.txt", "r");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}

	fseek(fp, 0, SEEK_END);

	int n = ftell(fp); //返回值:当前文件指针的位置

	printf("1.txt的总大小 =%d 字节\n", n);

	rewind(fp);//文件指针移到开头

	n = ftell(fp);

	printf("1.txt的总大小 =%d 字节\n", n);

	fclose(fp);

	return 0;
}
2)输出结果

3.13 实战练习:查单词

流程:

1.打开文件

  1. 循环

  2. 输入一个单词

  3. 遍历文件

  4. 打印出单词信息

  5. 重新开始查询

  6. 关闭文件  

 

1)示例代码
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{ 
    FILE *fp =fopen("./dict.txt","r");
    if(NULL == fp)
    {
        perror("fopen failed!\n");
        return -1;
    }

    char let[32] = {0};
    char str[100] = {0};

    while(1)
    {
        printf("please input letter:");
        fgets(let,sizeof(let),stdin);
        if(let[0] == '\n' )
        {
            break;
        }
        int n = strlen(let);
        rewind(fp);
        while(1)
        {
            char *p = fgets(str,sizeof(str),fp);
            if(NULL == p)
            {
                printf("fgets failed!\n");
                break;
            }
            if(strncmp(let,str,n-1)==0)
            {
                printf("%s\n",str);
                break;
            }
            memset(str,0,sizeof(str));
        }
        memset(let,0,sizeof(let));
    }
    fclose(fp);
    return 0;
}
2)输出结果

 3.14 格式化输出(fprintf、sprintf)

1. fprintf(将格式化数据打印到流中)
int  fprintf(FILE  *stream,  const  char *format, ...);
功能:将格式化数据打印到流中
参数:
  stream: 流指针
  format: 格式控制符
  ...:不定参数

 fprintf( fp, "%d-%d-%d" , 2022,7,21);
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./a.txt", "w");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}

	fprintf(fp, "%d-%d-%d %d:%d:%d\n", 2022,7,21,11,23,30);

	fclose(fp);

	return 0;
}

2)输出结果 

 

 2.sprintf(打印到字符串中)
int sprintf(char *str, const char *format, ...);
功能:
参数:
      str: 内存地址
      format: 格式控制符
      ...:不定参数
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	char buf[1024] = {0};


	sprintf(buf, "%d-%d-%d %d:%d:%d %f    %s%c", 2022,7,21,
			11,28,30, 3.14, "hello", 'a');


	printf("%s\n", buf);

	return 0;
}
2)输出结果

 

3.15 格式化输入(sscanf、fscanf) 

1.sscanf(从内存中提取数据出来) 
int sscanf(const char *str, const char *format, ...);
功能:从内存中提取数据出来
参数:
  str: 内存地址
  format: 格式控制符
  ...:不定参数
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	char buf[100] = "2022-7-21 11:40:30 hello";


	int a,b,c,d,e,f;

	char str[32] = {0};

	sscanf(buf, "%d-%d-%d %d:%d:%d %s", &a,&b,&c,&d,&e,&f,str);

	printf("%d\n", a);
	printf("%d\n", b);
	printf("%d\n", c);
	printf("%d\n", d);
	printf("%d\n", e);
	printf("%d\n", f);
	printf("%s\n", str);

	return 0;
}
2)输出结果

 

2.fscanf(从文件(流指针)中提取数据出来) 
int fscanf(FILE *stream, const char *format, ...);
功能:从文件(流指针)中提取数据出来
参数:
  stream: 流指针
  format: 格式控制符
  ...:不定参数
1)示例代码
      //(注意为了清晰无误地表示出不同的空白符,用 <\b>代表空格,<\t>表示制表符,<\n>表示换行符。)
      #include <stdio.h>
      #include <errno.h>
      
      void fscanfTest(FILE* fp) {
        char c1, c2, s1[100], s2[100];
        int d;
      
        // 第一部分:fscanf对空格的处理
        printf("the content of file is:\n");
        printf("hello<\\b>world<\\b><\\b>666lucky<\\n>");
        printf("\n\n");
      
        // %s不会跳过后面的空格
        fscanf(fp, "%s", s1);
        printf("%s!\n", s1);  // hello!
      
        // %s会跳过前面的一个空格
        rewind(fp);   // 将光标移回文件开头
        fscanf(fp, "%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // hello! world!
      
        // %*s会从文件流中读入,但是不会将值赋予变量(*的作用)
        rewind(fp);
        fscanf(fp, "%*s%s", s1);
        printf("%s!\n", s1);  // world!
      
        // %s会跳过前面的多个空格
        rewind(fp);
        fscanf(fp, "%*s%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // world! 666lucky!
      
        // %c不会跳过空格
        rewind(fp);
        fscanf(fp, "%*s%c", &c1);
        printf("%c!\n", c1); // " !"
      
        // format str中的一个空格表示如果文件流接下来有连续空格,都跳过
        rewind(fp);
        fscanf(fp, "%*s%*s %c", &c1);
        printf("%c!\n", c1);          // "6!"
        rewind(fp);
        fscanf(fp, "%*s%*s%*d%c", &c1);
        printf("%c!\n", c1);          // "l!" 
        rewind(fp);
        fscanf(fp, "%*s%*s%*d %c", &c2);   // 注意这里format str中的空格没起作用,是因为666和lucky之间没有空白符
        printf("%c!\n", c2);          // "l!"
        rewind(fp);
        fscanf(fp, "%*s%*s%*d%s", s1);
        printf("%s!\n", s1);          // "lucky!" 
        rewind(fp);
        fscanf(fp, "%*s%*s%*d %s", s2);
        printf("%s!\n", s2);          // "lucky!"
      
        // format str中的多个连续空格和一个空格的效果是一样的
        rewind(fp);
        fscanf(fp, "%*s %c", &c1);
        printf("%c!\n", c1); // "w!"
        rewind(fp);
        fscanf(fp, "%*s  %c", &c2);
        printf("%c!\n", c2); // "w!"
      
        // 第二部分:fscanf对制表符的处理
        printf("the content of file is:\n");
        printf("hello<\\t>world<\\t><\\t>666lucky<\\n>");
        printf("\n\n");
      
        // %s不会跳过后面的制表符
        fscanf(fp, "%s", s1);
        printf("%s!\n", s1);  // hello!
      
        // %s会跳过前面的一个制表符
        rewind(fp);
        fscanf(fp, "%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // hello! world!
      
        // %s会跳过前面的多个制表符
        rewind(fp);
        fscanf(fp, "%*s%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // world! 666lucky!
      
        // %c不会跳过制表符
        rewind(fp);
        fscanf(fp, "%*s%c", &c1);
        printf("%c!\n", c1); // "<\\t>!"
      
        // format str中的一个制表符表示如果文件流接下来有连续制表符,都跳过
        rewind(fp);
        fscanf(fp, "%*s%*s\t%c", &c1);
        printf("%c!\n", c1);          // "6!"
        rewind(fp);
        fscanf(fp, "%*s%*s%*d%c", &c1);
        printf("%c!\n", c1);          // "l!" 
        rewind(fp);
        fscanf(fp, "%*s%*s%*d\t%c", &c2);
        printf("%c!\n", c2);          // "l!"
        rewind(fp);
        fscanf(fp, "%*s%*s%*d%s", s1);
        printf("%s!\n", s1);          // "lucky!" 
        rewind(fp);
        fscanf(fp, "%*s%*s%*d\t%s", s2);
        printf("%s!\n", s2);          // "lucky!"
      
        // format str中的多个连续制表符和一个制表符的效果是一样的
        rewind(fp);
        fscanf(fp, "%*s\t%c", &c1);
        printf("%c!\n", c1); // "w!"
        rewind(fp);
        fscanf(fp, "%*s\t\t%c", &c2);
        printf("%c!\n", c2); // "w!"
      
        // 第三部分:fscanf对换行符的处理
        printf("the content of file is:\n");
        printf("hello<\\n>world<\\n><\\n>666lucky<\\n>");
        printf("\n\n");
      
        // %s不会跳过后面的换行符
        fscanf(fp, "%s", s1);
        printf("%s!\n", s1);  // hello!
      
        // %s会跳过前面的一个换行符
        rewind(fp);
        fscanf(fp, "%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // hello! world!
      
        // %s会跳过前面的多个换行符
        rewind(fp);
        fscanf(fp, "%*s%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // world! 666lucky!
      
        // %c不会跳过换行符
        rewind(fp);
        fscanf(fp, "%*s%c", &c1);
        printf("%c!\n", c1); // "<\\n>!"
      
        // format str中的一个换行符表示如果文件流接下来有连续换行符,都跳过
        rewind(fp);
        fscanf(fp, "%*s%*s\n%c", &c1);
        printf("%c!\n", c1);          // "6!"
        rewind(fp);
        fscanf(fp, "%*s%*s%*d%c", &c1);
        printf("%c!\n", c1);          // "l!" 
        rewind(fp);
        fscanf(fp, "%*s%*s%*d\n%c", &c2);
        printf("%c!\n", c2);          // "l!"
        rewind(fp);
        fscanf(fp, "%*s%*s%*d%s", s1);
        printf("%s!\n", s1);          // "lucky!" 
        rewind(fp);
        fscanf(fp, "%*s%*s%*d\n%s", s2);
        printf("%s!\n", s2);          // "lucky!"
      
        // format str中的多个连续换行符和一个换行符的效果是一样的
        rewind(fp);
        fscanf(fp, "%*s\n%c", &c1);
        printf("%c!\n", c1); // "w!"
        rewind(fp);
        fscanf(fp, "%*s\n\n%c", &c2);
        printf("%c!\n", c2); // "w!"
      
        // 第四部分:当空格、制表符以及换行符混杂时fscanf的处理
        printf("the content of file is:\n");
        printf("hello<\\b><\\t><\\n>world<\\t><\\b><\\n>666lucky<\\n>");
        printf("\n\n");
      
        // %s会跳过连在一起的空格、制表符和换行符
        fscanf(fp, "%s%s", s2, s1);
        printf("%s! %s!\n", s2, s1);  // hello! world!
      
        // 当作为空白符时,format str中的空格、制表符以及换行符是一样的,可以相互替代!
        rewind(fp);
        fscanf(fp, "%*s %c", &c1);
        printf("%c!\n", c1);  // "w!"
        rewind(fp);
        fscanf(fp, "%*s\t%c", &c2);
        printf("%c!\n", c2);  // "w!"
        rewind(fp);
        fscanf(fp, "%*s\n%c", &c1);
        printf("%c!\n", c1);  // "w!"
      
        // 第五部分:[]符号在format str中的应用
        printf("the content of file is:\n");
        printf("hello<\\b><\\t><\\n>world<\\b><\\t>666lucky<\\n>");
        printf("\n\n");
      
        // [el]表示只读取'e'或者'l'这个字符,[0-9]表示只读取0-9这10个数字字符
        // %[]之后的域都不起作用了,不会读取文件流。
        // test#1: %c%[]s可以正常工作
        // output#1: h! ell!
        errno = 0;
        d = fscanf(fp, "%c%[el]s", &c1, s1);
        if (d == 2) printf("%c! %s!\n", c1, s1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#2: %[]s后面的%c没有正常读取
        // output#2: d = 2
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%c%[el]s%c", &c2, s2, &c1);
        if (d == 3) printf("%c! %s! %c!\n", c2, s2, c1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#3: %[]s后面的%s没有正常读取
        // output#3: d = 2
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%c%[el]s%s", &c1, s1, s2);
        if (d == 3) printf("%c! %s! %s!\n", c1, s1, s2);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#4: 再次运行fscanf函数就可以继续读取文件流
        // output#4: o! world!
        errno = 0;
        d = fscanf(fp, "%c%s", &c2, s2);
        if (d == 2) printf("%c! %s!\n", c2, s2);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
      
        // [^el]表示不读取'e'也不读取'l'这个字符,[^0-9]表示不读取0-9的数字字符
        // %[^]之后的域都不起作用了,不会读取文件流。
        // test#5: %c%[^]s可以正常工作,注意下面的%[^w]s这个域读取了空格、制表符以及换行符。
        // output#5: h! ello<\\b><\\t><\\n>!
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%c%[^w]s", &c1, s1);
        if (d == 2) printf("%c! %s!\n", c1, s1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#6: %[^]s后面的%s没有正常读取
        // output#6: d = 2
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%c%[^w]s%s", &c2, s2, s1);
        if (d == 3) printf("%c! %s! %s!\n", c2, s2, s1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#7: 再次运行fscanf函数就可以继续读取文件流
        // output#7: w! orld!
        errno = 0;
        d = fscanf(fp, "%c%s", &c1, s1);
        if (d == 2) printf("%c! %s!\n", c1, s1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#8: %[^\n]s可以一直读取到行末尾,哪怕遇到空格或者制表符。
        // output#8: h! ello<\\b><\\t>!
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%c%[^\n]s", &c2, s2);
        if (d == 2) printf("%c! %s!\n", c2, s2);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
        // test#9: %[^ ]s不会读取空格,但是会读取制表符和换行符
        // output#9: <\\t><\\n>world!
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%*s%*c%[^ ]s", s1);
        if (d == 1) printf("%s!\n", s1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }  
        // test#10: %[^\t]s不会读取制表符,但是会读取空格和换行符
        // output#10: <\\n>world<\\b>!
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%*s%*c%*c%[^\t]s", s2);
        if (d == 1) printf("%s!\n", s2);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        } 
        // test#11: %[^]s不会跳过前面的空白符
        // output#11: <\\b><\\t><\\n>wo!
        errno = 0;
        rewind(fp);
        d = fscanf(fp, "%*s%[^r]s", s1);
        if (d == 1) printf("%s!\n", s1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
      
        // 第六部分:出错的情况
        // 从第五部分 test#2 以及 test#3 的例子中可以看出,fscanf的返回值表示能够正确赋值的域的个数。如果出错,fscanf返回EOF。
        // 怎样才算出错?如果还没有任何一个域匹配成功或者任何一个匹配失败发生之前,就达到了文件流末尾,就算出错;或者读取文件流出错。就这两种情况。
        // 即使所有域都不匹配,但只要没到达文件流末尾并且读取文件流过程中没有发生错误,就不算出错,errno就是0。此时,fscanf返回0。
        printf("the content of file is:\n");
        printf("hello");
        printf("\n\n");
      
        // test#1: 此时的%c发生匹配失败,所以返回值为0。
        // output#1: d = 0
        errno = 0;
        d = fscanf(fp, "%*s%c", &c1);
        if (d == 1) printf("%c!\n", c1);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }   
        // test#2: 继续读取,已经到达文件流末尾,返回EOF。
        // output#2: d = -1
        errno = 0;
        d = fscanf(fp, "%c", &c2);
        if (d == 1) printf("%c!\n", c2);
        else {
          printf("d = %d\n", d);
          if (errno != 0) perror("fscanf");
          else printf("Error: no matching characters!\n");
        }
      }
      
      int main(int argc, char* argv[]) {
        FILE *fp;
        if (argc < 2) {
          printf("Usage: %s <filename>\n", argv[0]);
          return 1;
        }
        if ((fp = fopen(argv[1], "r")) == NULL) {
          printf("Error: cannot open file\n");
          return 1;
        }
        fscanfTest(fp);
        fclose(fp);
        return 0;
      }
2)输出结果 

 

3.16 时间函数(time、locatime) 

1.time(从1970-1-1 0:0:0开始到现在的秒数) 
#include <time.h>
#include <unistd.h>

time_t t;
time_t time(time_t *t);
功能:从1970-1-1 0:0:0开始到现在的秒数
参数:
	t:  &t / NULL
返回值:
    成功:从1970-1-1 0:0:0开始到现在的秒数
    失败:-1
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	time_t t;

	while(1)
	{
		int m = time(&t);
		
		printf("秒数:%d\n", m);
	
		sleep(1);
	}

	return 0;
}
2)输出结果 

 

 2.localtime(从1970-1-1 0:0:0开始到现在的秒数转化成日历)
#include <time.h>

time_t t;
struct tm *localtime(const time_t *timep);
功能:从1970-1-1 0:0:0开始到现在的秒数转化成日历
参数:
	timep: &t
返回值:
    成功:struct tm *
    失败:NULL


struct tm {
   int tm_sec;    /* Seconds (0-60) */
   int tm_min;    /* Minutes (0-59) */
   int tm_hour;   /* Hours (0-23) */
   int tm_mday;   /* Day of the month (1-31) */
   int tm_mon;    /* Month (0-11) */
   int tm_year;   /* Year - 1900 */
};
1)示例代码 
/***********************************************/
练习:获取系统时间,并将系统时间 打印并输出到 文件中
	[2024-04-07 14:07:30]
	[2024-04-07 14:07:31]
	[2024-04-07 14:07:32]
/************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fopen("./time.txt", "a");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	while(1)
	{
		time_t t;
		time(&t);
		struct tm *p = localtime(&t);
		printf("%d-%d-%d %d:%d:%d\n", p->tm_year+1900,
				p->tm_mon+1,p->tm_mday,
				p->tm_hour,p->tm_min,p->tm_sec
			  );

		fprintf(fp, "%d-%d-%d %d:%d:%d\n", p->tm_year+1900,
				p->tm_mon+1,p->tm_mday,
				p->tm_hour,p->tm_min,p->tm_sec
			  );
		fflush(fp);
		sleep(1);
	}
	fclose(fp);
	return 0;
}
2)输出结果

 

4.文件I/O

4.1 文件描述符

文件描述符实际上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,进程使用它来标识打开的文件。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

 Linux系统为程序中每个打开的文件分配一个文件描述符,文件IO操作通过文件描述符来完成。

文件描述符在形式上是一个顺序分配的非负整数。从0开始分配,依次递增。比如 0,1,2表示 stdin stdout stderr,一般最大打开的文件描述符数量为1024(0~1023)

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

int main(int argc, const char *argv[])
{
	printf("%d\n", stdin->_fileno);
	printf("%d\n", stdout->_fileno);
	printf("%d\n", stderr->_fileno);
	return 0;
}

 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

4.2 fdopen(文件IO转化标准IO)

FILE *fdopen(int fd, const char *mode);
功能:把文件IO转化成标准IO
参数;
      fd: 文件描述符
      mode: 打开方式
返回指:
      成功:FILE *
      失败:NULL
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	FILE *fp = fdopen(1, "w");

	fprintf(fp, "%d-%d-%d\n", 2022,7,21);

	return 0;
}
2)输出结果

 

4.3 常用的文件I/O函数 

 /open/close/read/write/lseek

4.4 open(打开文件) 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//打开文件,文件已存在
int open(const char *pathname, int flags);
功能:打开指定的文件
参数:
	参数1:所需打开文件的名字(包含路径)
	参数2:打开文件的方式---》主标志 | 副标志
返回值:成功代表一个大于0 的数字(文件描述符),失败返回-1(errno ie set...)

//创建文件,文件不存在
int open(const char *pathname, int flags, mode_t mode);
参数:
      pathname: 文件的路径名 
	  flags: 
		    O_RDONLY:只读    ①
            O_WRONLY:可写    ②
		    O_RDWR: 读写     ③
            O_CREAT :创建    ④
            O_TRUNC :清空    ⑤
            O_APPEND:追加    ⑥
            O_EXCL:如果O_CREAT时文件存在,可返回错误消息。
            实际创建的文件权限需要经过一个公式计算得到: mode & (~umask)
      
      mode: 权限
返回指:    
      成功:文件描述符fd
      失败:-1

 标准IO                  文件IO
    r                      O_RDONLY
    r+                    O_RDWR
    w                     O_WRONLY | O_CREAT | O_TRUNC
    w+                   O_RDWR   | O_CREAT | O_TRUNC
    a                      O_WRONLY | O_CREAT | O_APPEND
    a+                    O_RDWR   | O_CREAT | O_APPEND

1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd = open("./6.txt", O_CREAT|O_RDWR, 0666);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("fd=%d\n", fd);
	printf("open success!\n");
	close(fd);
	return 0;
}
2)输出结果

 

4.5 close(关闭文件)

#include <unistd.h>

int close(int fd);

4.6 read(读取数据)

ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
参数:
      fd: 文件描述符:open函数的返回值
      buf: 内存地址 char buf[];
	  count: 读取的字节数----->>sizeof测得
返回值:
      成功:返回实际读取到的字节数n
			     n == 0 取到文件末尾
				 n > 0  成功读取到的字节数
      失败:n<0	读取失败
1)示例代码(统计文件的大小)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd = open("./1.txt", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("fd=%d\n", fd);
	printf("open success!\n");
	char buf[2000] = {0};
	int count = 0;
	while(1)
	{
		int n = read(fd, buf, sizeof(buf));
		if(n< 0)
		{
			perror("read");
			return -1;
		}
		else if(n == 0) //读到文件的末尾了
		{
			break;
		}
		count += n;
		printf("n=%d\n", n);
		printf("buf:%s\n", buf);
		memset(buf, 0 ,sizeof(buf));
	}
	printf("count=%d\n", count);
	close(fd);
	return 0;
}
2)输出结果 

 

4.7 write(数据写入)

ssize_t  write(int  fd,  const void *buf, size_t count);
功能:把内存中的数据写入到文件中
参数:
      fd: 文件描述符
      buf: 内存地址
	  count: 写入的字节数
返回值:
     成功:实际写入的字节数n
			  n>=0
     失败:    n<0
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd = open("./6.txt", O_CREAT|O_RDWR, 0666);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("fd=%d\n", fd);
	printf("open success!\n");
	char buf[100] = {0};
	while(1)
	{
		gets(buf);
		int n = write(fd, buf, strlen(buf));
		printf("n=%d\n", n);
	}
	close(fd);
	return 0;
}
2)输出结果

4.8 练习

1)复制文件内容:由1.txt复制到2.txt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	if(argc!=3)
	{
		printf("%s src_file dest_file\n", argv[0]);
		return -1;
	}
	int fd1 = open( argv[1] , O_RDONLY);
	if(fd1 < 0)
	{
		perror("open");
		return -1;
	}
	int fd2 = open( argv[2] , O_WRONLY|O_CREAT|O_TRUNC, 0666);
	if(fd2 < 0)
	{
		perror("open");
		return -1;
	}
	printf("fd1=%d fd2=%d\n", fd1, fd2);
	char buf[100] = {0};
	while(1)
	{
		int n = read(fd1, buf, sizeof(buf));
		if(n< 0)
		{
			perror("read");
			return -1;
		}
		else if(n == 0) //读到文件的末尾了
		{
			break;
		}
		write(fd2, buf, n);
		memset(buf, 0 ,sizeof(buf));
	}
	printf("copy success!\n");
	close(fd1);
	close(fd2);
	return 0;
}
 2)输出结果

4.9 lseek(定位文件指针)

off_t lseek(int fd, off_t offset, int whence);
功能:定位文件指针
参数:
      fd: 文件描述符
      offset: 偏移量
            100:向后偏移100个字节
            -100:向前偏移100个字节
      whence: 基点
            SEEK_SET:文件开头
            SEEK_END:文件末尾
            SEEK_CUR:文件当前位置
返回值:
    成功:0
    失败:-1

lseek(fd, 0, SEEK_END);
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd = open("./test.txt", O_CREAT|O_RDWR, 0666);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("fd=%d\n", fd);
	printf("open success!\n");
	write(fd, "hello", 5);
	lseek(fd, 100, SEEK_END);
	write(fd, "world", 5);
	close(fd);
	return 0;
}
2)输出结果

4.10 实践练习: 使用文件IO的函数加密图片 (音频、视频)

1)示例代码
/*===============================================
*   文件名称:jiami_1.c
*   创 建 者: 肖立生
*   创建日期:2022年07月22日
*   描    述:加密解密照片
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	if(argc!=2)
	{
		printf("%s picture\n", argv[0]);
		return -1;
	}

	int fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	char buf[10] ={0};
	read(fd, buf, 10);

	int i;
	for(i=0; i<5; i++)
	{
		buf[i] ^= buf[9-i];
		buf[9-i] ^= buf[i];
		buf[i] ^= buf[9-i];
	}
	lseek(fd, 0 , SEEK_SET);
	write(fd, buf, 10);
	printf("加密解密成功!\n");
	close(fd);
	return 0;
}

2)输出结果
 未加密前

加密后 

 

解密继续运行编译代码就能解密

 4.11 目标操作符(opendir、readdir、closedir)

 1.opendir(打开目录)
#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
功能:用来打开参数name 指定的目录, 并返回DIR*形态的目录流, 接下来对目录的读取和搜索都要使用此返回值
参数:
      name:指定的目录
返回值:
      成功: DIR *
      失败: NULL
2.readdir(读取目录信息) 
struct dirent *readdir(DIR *dirp);
功能:读取目录的信息
参数:
      dirp: opendir函数的返回值
返回值:
      成功: struct dirent *
      失败: NULL(一直遍历,直到遍历目录完成返回NULL)


struct dirent {
   ino_t          d_ino;       /* inode number */
   off_t          d_off;       /* not an offset; see NOTES */
   unsigned short d_reclen;    /* length of this record */
   unsigned char  d_type;      /* type of file; not supported
								  by all filesystem types */
   char           d_name[256]; /* filename */
};
 3.closedor(关闭目录)
int closedir(DIR *dirp);
 4.实践练习:实现查看目录下的文件
 1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>

int main(int argc, const char *argv[])
{
	if(argc!=2)
	{
		printf("%s 目录名\n", argv[0]);
		return -1;
	}
	DIR *dirp = opendir(argv[1]);   //打开目录
	if(dirp == NULL)
	{
		perror("opendir");
		return -1;
	}
	while(1)
	{
		struct dirent *p = readdir(dirp);   //读取目录信息
		if( p == NULL )
		{
			break;
		}

		if( strncmp(p->d_name, ".", 1) == 0 )
		{
			continue;
		}
		printf("%s ", p->d_name);
	}
	puts("");  //printf("\n");  putchar('\n');
	closedir(dirp);  //关闭目录
	return 0;
}
2)输出结果

4.12 文件信息函数(stat、getpwuid、getgrgid)

1.stat(查看文件信息)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

struct stat mybuf;

int stat(const char *path, struct stat *buf);
功能:查看文件信息
参数:
      path:文件的路径名
	  buf: &mybuf
-rw-rw-r-- 1 farsight farsight   593  4月 19 09:26 01picture.c
-rw-rw-r-- 1 farsight farsight   537  4月 19 09:53 02readdir.c
-rw-rw-r-- 1 farsight farsight   161  4月 19 10:23 03stat.c
-rwxrw-rw- 1 farsight farsight 97893  4月 19 09:26 5.jfif
-rwxrwxr-x 1 farsight farsight  7357  4月 19 09:53 a.out
    
//查看设备号
major   minor
  
struct stat {
	dev_t     st_dev;     /* ID of device containing file */
	ino_t     st_ino;     /* inode number */
	mode_t    st_mode; 文件的权限、文件的类型   /* protection */
	nlink_t   st_nlink;   /* number of hard links */
	uid_t     st_uid;  所属用户ID   /* user ID of owner */ 
	gid_t     st_gid;  所属组ID   /* group ID of owner */
	dev_t     st_rdev;    /* device ID (if special file) */
	off_t     st_size; 文件的大小   /* total size, in bytes */
	blksize_t st_blksize; /* blocksize for filesystem I/O */
	blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
	time_t    st_atime; 最后一次访问时间  /* time of last access */
	time_t    st_mtime; 最后一次修改时间  /* time of last modification */
	time_t    st_ctime; 最后一次文件文件属性修改时间  /* time of last status change */
};
2.getpwuid(用户名)
struct passwd *getpwuid(uid_t uid);


struct passwd {
   char   *pw_name;       /* username */
   char   *pw_passwd;     /* user password */
   uid_t   pw_uid;        /* user ID */
   gid_t   pw_gid;        /* group ID */
   char   *pw_gecos;      /* user information */
   char   *pw_dir;        /* home directory */
   char   *pw_shell;      /* shell program */
};
 3.getgrgid(组名)
struct group *getgrgid(gid_t gid);

struct group {
	char   *gr_name;       /* group name */
	char   *gr_passwd;     /* group password */
	gid_t   gr_gid;        /* group ID */
	char  **gr_mem;        /* group members */
};
 4.实践练习:实现ls -l同等功能

 文件类型
{
    常规文件:S_ISREG     '-'
    目录:S_ISDIR         'd'
    字符设备:S_ISCHR     'c'
    块设备:S_ISBLK     'b'
    管道:S_ISFIFO         'p'
    套接字:S_ISSOCK     's'
    符号链接:S_ISLNK     'l'
}

1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <sys/sysmacros.h>
#include <grp.h>
#include <dirent.h>

int main(int argc, const char *argv[])
{
    if(argc!=2)
    {
        printf("%s 文件路径名\n", argv[0]);
        return -1;
    }
    DIR *dirp = opendir(argv[1]);
    if(dirp == NULL)
    {
        perror("opendir failed!\n");
        return -1;
    }
    chdir(argv[1]); //改变工作目录
    while(1)
    {
        struct dirent *p= readdir(dirp);
        if( p == NULL )
        {
            break;
        }
        while(NULL != p)
        {
            if( strncmp(p->d_name, ".", 1) == 0 )
            {
                break;
            }
            struct stat mybuf;
            stat(p->d_name, &mybuf);

            //文件类型

            if(S_ISREG(mybuf.st_mode))
            {
                printf("-");
            }
            else if(S_ISDIR(mybuf.st_mode))
            {
                printf("d");
            }
            else if(S_ISCHR(mybuf.st_mode))
            {
                printf("c");
            }
            else if(S_ISBLK(mybuf.st_mode))
            {
                printf("b");
            }
            else if(S_ISFIFO(mybuf.st_mode))
            {
                printf("p");
            }
            else if(S_ISLNK(mybuf.st_mode))
            {
                printf("l");
            }
            else if(S_ISSOCK(mybuf.st_mode))
            {
                printf("s");
            }
            //文件权限
            printf("%c", (mybuf.st_mode & (0x1<<8) )?'r':'-');
            printf("%c", (mybuf.st_mode & (0x1<<7) )?'w':'-');
            printf("%c", (mybuf.st_mode & (0x1<<6) )?'x':'-');
            printf("%c", (mybuf.st_mode & (0x1<<5) )?'r':'-');
            printf("%c", (mybuf.st_mode & (0x1<<4) )?'w':'-');
            printf("%c", (mybuf.st_mode & (0x1<<3) )?'x':'-');
            printf("%c", (mybuf.st_mode & (0x1<<2) )?'r':'-');
            printf("%c", (mybuf.st_mode & (0x1<<1) )?'w':'-');
            printf("%c", (mybuf.st_mode & (0x1<<0) )?'x':'-');


            //链接数
            printf(" %-2ld  ", mybuf.st_nlink);
            //用户名
            struct passwd *m = getpwuid(mybuf.st_uid);
            printf(" %-5s  ", m->pw_name);
            //组名
            struct group *q = getgrgid(mybuf.st_gid);
            printf(" %-5s  ", q->gr_name);
            //文件大小
            printf(" %-5ld  ", mybuf.st_size);
            //最后一次修改的时间

            struct tm *k = localtime(&mybuf.st_mtime);
            printf(" %-2d %-2d %-2d:%-2d ", k->tm_mon+1,k->tm_mday,k->tm_hour,k->tm_min);

            //文件名
            printf("%s\n ",p->d_name);
            p = readdir(dirp);
        }
    }
    closedir(dirp);
    return 0;
}

2)输出结果
ls -l 

 a.out

5.实践练习:实现ls -a功能 
 1)示例代码
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

int main(int argc, char *argv[])
{
	//功能:实现打开目录,并读取目录中的文件的filename

	//(1)opendir
	DIR *pDir = opendir(argv[1]);
	if(NULL == pDir)
	{
		perror("opendir error");
		return -1;
	}
	printf("opendir ok!\n");

	//(2)readdir
	while(1)
	{
		struct dirent *pSD = readdir(pDir);
		if(NULL == pSD)
		{
			break;
		}
		//使用pSD该结构体指针来访问结构体中的文件名
		printf("%s\n",pSD->d_name);
	}
	closedir(pDir);
	return 0;
}

2)输出结果
ls -a

a.out

 

三、 库的制作

 1.什么是库

库是写好的,现有的,成熟的,可以复用的代码。本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。

/lib/i386-linux-gnu

/usr/include

  1. 库是一种加密的二进制文件

  2. 需要被操作系统载入内存运行

  3. 相比于可执行程序,它不可以直接运行

  4. window 和 linux 都有自己的库,但是不兼容

  5. Linux系统的库有两种,1. 静态库 2. 共享库(又叫动态库)

了解:                     静态库        动态库
linux                    *.a                  *.so

//window             *.lib                *.dll

2.静态库的制作和使用 

 2.1 制作

$ gcc -c xxx.c -o xxx.o

$ ar -crs libxxx.a xxx.o

静态库的命名规范: ​

        必须以lib开头,紧跟库的名字,跟扩展名 .a ​

        例如: libxxx.a

 2.2 使用

 $ gcc (-o xxx) main.c -L路径 -lxxx

        -o xxx:编译生成文件,与a.out等同,可设置也可不设置

                     设置后运行:./xxx

                     未设置运行:./a.out

        -L: 指定静态库所在的目录

        -l: 指定静态库的名字 xxx部分

2.3 运行

优点:a.out 运行后不需要库,可以直接运行

缺点: 每个a.out都要包含库,体积较大, 浪费资源;

            对程序更新,部署,发布带来麻烦;

3.动态库的制作和使用

3.1 制作

$ gcc -fPIC -c xxx.c -o xxx.o

$ gcc -shared -o libxxx.so xxx.o

动态库的命名规范

        必须以lib开头,紧跟库的名字,跟扩展名 .so

        例如: libxxx.so

3.2 使用

$ gcc (-o xxx) main.c -L路径 -lxxx

        -o xxx:编译生成文件,与a.out等同,可设置也可不设置

                     设置后运行:./xxx

                     未设置运行:./a.out

        -L: 指定静态库所在的目录

        -l: 指定静态库的名字 xxx部分

$ ldd a.out # 用于查看可执行程序依赖的动态库有哪些

3.3 运行

 $ ./a.out # 会报错 (7146)

动态库的搜索方式(3种,任意选一种):

1.将动态库拷贝到 /lib/ 或者 /usr/lib/

        $ sudo cp libxxx.so /usr/lib/

2.export LD_LIBRARY_PATH=. 或者so所在的路径 (临时情况)

3.pwd

   cd /etc/ld.so.conf.d

   ls sudo vi my.conf

   添加路径

   sudo ldconfig 生效

特点:在编译时不会链接到可执行文件中,只是再其中保存一个索引,在运行时,才真正的链接(动态),因此可执行程序体积小。

优点: a.out 体积较小, 节约资源; 只需要修改.so动态库,有利于程序的更新,部署,发布;

缺点:a.out 运行后需要库,不能直接运行。

4.静态库和动态库的区别 

1.静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。

2.静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行;动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。

静态库动态库最本质的区别就是:该库是否被编译进目标(程序)内部

静态(函数)库
        一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。

        这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。(在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库,因此体积较大)

动态(函数)库
        动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。

        与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。(在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小)

四、进程 

1.进程相关概念

程序:
          用计算机语言对算法的具体实现,它是静态的
进程(process):
          a.正在运行的程序,它是动态的(是一个程序一次执行的过程)
          b.资源管理的最小单位  (每一个进程都分配一个虚拟的4G内存)
          c.是程序执行的过程,包括创建、调度和消亡
虚拟内存空间:
          a.核空间:1G
          b.用户空间:0-3G 

          c.内核:3G-4G
     

堆、栈 、代码段 、数据段 、不可访问空间

进程号PID:唯一标识一个进程

       进程结束后,用户空间会随着进程的结束而释放,但内核空间不会,这1G空间需要创建它的父进程来回收,否则会称为僵尸进程(Bug) 

2.进程和程序的区别 

本质的区别:
1) 程序(Program)
   - 程序是一系列指示计算机执行任务的命令,通常以源代码或目标代码的形式存在
   - 它是静态的,存储在存储器中,直到被加载并执行。
   - 程序由计算机指令和数据组成,这些指令和数据按照特定的顺序排列,用来解决问题或完成特定的任务。
   - 程序本身不具备执行能力,需要通过计算机的处理器来执行。


2) 进程(Process)
   - 进程是程序在计算机中执行的过程,是系统进行资源分配和调度的基本单位
   - 进程是动态的,它包括程序计数器、寄存器和变量的当前值等,是程序在处理器上的一次动态执行过程。
   - 进程具有独立的执行流,每个进程都有自己的虚拟地址空间、内存、数据栈以及其他用于跟踪执行状态的辅助数据。
   - 操作系统通过进程来管理计算机的多任务处理,它可以同时运行多个进程,每个进程都好像在独立运行一样。
        

        简而言之,程序是静态的代码集合,而进程是程序在执行过程中动态的表现当程序被加载到内存中并由操作系统执行时,它就变成了一个进程。 

 3.进程的通用内存管理

正文段、用户数据段、系统数据段

        在操作系统中,进程的内存通常被分为几个不同的段或区域,每种段都有其特定的用途。正文段、用户数据段和系统数据段是进程内存中的三种主要段:
1)正文段(Text Segment)
   - 正文段也称为代码段,包含了进程执行的机器指令。
   - 它是只读的,因为一旦程序被加载到内存中,其机器指令不应被修改。
   - 正文段通常包含了程序的入口点,即程序开始执行的地址。
   - 在某些操作系统中,如Mach或Windows,正文段可能被分为多个部分,如只读的代码部分和可写的初始化代码部分。


2)用户数据段(User Data Segment)
   - 用户数据段用于存储进程运行时创建的数据,如局部变量、函数参数、返回值等。
   - 它供程序中的函数使用,并且通常在函数调用期间由程序管理。
   - 用户数据段是可读写的,因为程序需要读写这些数据来执行任务。


3)系统数据段(System Data Segment)
   - 系统数据段用于存储系统运行时所需的信息,如全局变量、静态变量等。
   - 它也可能包含某些操作系统特定的信息,如线程局部存储(TLS)数据。
   - 系统数据段通常是只读的,以保证这些数据的一致性和安全性。

        除了这三种主要段,还有一些其他的内存段可能存在于进程的地址空间中,例如堆(Heap),它是用于动态分配内存的区域,通常由程序在运行时请求和释放堆栈(Stack)用于存储函数调用的上下文信息,局部变量等,它在函数调用期间由调用者自动管理。

        不同的操作系统和编译器可能会有不同的内存段划分方式,但上述提到的正文段、用户数据段和系统数据段是较为通用的分类。这些段的划分有助于提高程序的可维护性,确保程序的正确执行,并为操作系统提供有效的内存管理手段。

        以下是进程内存管理的一些关键点(涉及到进程如何申请、使用和释放内存资源):
内存分配、内存映射、内存保护、内存共享、内存交换(Swapping)和分页(Paging)、内存释放、内存管理单元(MMU)

 4.进程的类型

1.交互进程(ctrl+z / jobs -l / bg / fg / kill -l /kill -9 PID /ps -ajx)
        1)shell命令进程,文本编辑器(vim),图形应用程序等
        2)既有前台进程,又有后台进程
        3) fg+ 作业号 :可以将后台运行的程序,切换到前台来运行

/*****************************************/

ctrl+z : 让进程变成后台进程

jobs -l: 查看后台的进程,叫做作业 16674:作业号

bg % 作业号:使后台进程恢复到前台,不能被ctrl+c结束(关闭终端即可)

fg % 作业号:使后台进程恢复到前台,能被ctrl+c结束

kill -l: 查看信号的种类

kill -9 PID: 结束相应的进程

ps -ajx: 查看进程的属性

/***************************************/

2.批处理进程(运维)
        该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
3.守护进程(1 init)
        该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。

5.不同系统查看进程 

windos
	任务管理器
Linux
	ps -aux
		显示不了状态
	ps -ef
		查看所有进程及其PID(进程号),系统时间,命令详细目录,执行者等
	ps axj
		查看系统当中的进程,也就是正在运行的程序
	top
		动态显示系统中运行的程序(一般每隔5s刷新一次)
	pstree
		以树形结构显示系统当中的进程
	kill
		输出特定的信号给指定PID(进程号)的进程‘
		-l  列出所有可用的信号名称.

6.进程的运行状态

ps -ajx

就绪态:进程已获得除CPU外的所有必要资源,只等待CPU时的状态。一个系统会将多个处于就绪状态的进程排成一个就绪队列。

运行态(R) TASK_RUNNING: 进程已获CPU,正在执行。单处理机系统中,处于执行状态的进程只一个;多处理机系统中,有多个处于执行状态的进程。此时进程或者正在进行,或者准备运行内核调度程序到CPU上执行 running

等待态 (阻塞态):正在执行的进程由于某种原因而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。(这种状态又称等待状态或封锁状态)

可中断的等待态  S  (标准输入,sleep())  (TASK_INTERRRUPTIBLE):如果进程收到信号会醒来 ctrl+c 
不可中断的等待态 D  (TASK_UNINTERRUPTIBLE):如果进程收到信号不会醒来                 

停止态T:此时进程被中止SIGSTOP

死亡态X:已终止的进程、僵尸进程 但还在进程向量数组中占有一个task_struct结构

                task_struct{

                        pid_t pid;

                        R; ...

                };

僵尸态(Z)EXIT_ZOMIE

Linux系统中,进程可以分为三种基本状态,这三种状态定义在`POSIX.1-2001`标准中,它们是:
1. **运行(Running)**:
   - 进程正在CPU上执行。
   - 在多道程序环境中,可能有许多进程处于运行状态,但任何时刻只能有一个进程在处理器上执行。
2. **阻塞(Blocked)**:
   - 进程在等待某个事件发生,比如等待输入/输出操作完成。
   - 进程在等待某些资源,如打开的文件或其他系统资源。
   - 阻塞状态的进程不会使用CPU。
3. **就绪(Ready)**:
   - 进程已经准备好开始执行,但尚未被调度执行。
   - 进程可能因为优先级较低或其他原因而等待被CPU调度。
   - 在多道程序设计系统中,就绪状态的进程等待被CPU选中并开始执行。
        

        除了这三种基本状态,还有一种终止状态(Terminated),表示进程已经完成执行并已从系统中移除。


Linux进程的状态转换通常涉及以下几种变迁:
- **运行到阻塞**:进程在执行过程中,请求的资源不可用,如进行I/O操作时,CPU会让出,进程状态从运行变为阻塞。
- **阻塞到就绪**:进程等待的事件发生,如I/O完成,进程状态从阻塞变为就绪,等待CPU调度。
- **就绪到运行**:进程被调度器选中,从就绪状态开始执行。
- **运行到就绪**:在多任务系统中,调度器可能会因为时间片到、更高优先级任务到达等原因,中断当前运行的进程,让其他就绪状态的进程运行,此时当前进程状态从运行变为就绪。
- **运行到终止**:进程完成其任务,自行终止或者因为某些错误被操作系统终止,状态变为终止。
        这些状态转换是操作系统调度进程、管理资源的基础,确保了系统的高效运行。在Linux系统中,可以使用`ps`命令等工具查看进程的状态信息。 

7.进程的优先级 

1.进程的优先级范围
        -20到19,数字越大,优先级越低
2.nice:以某种优先级执行我们的程序
3.renice :可以修改正在运行程序的优先级

< 高优先级
​    N 低优先级
​    L 有些页被锁进内存
​    s 会话组组长

+  位于前台的进程组
l  多线程,克隆线程

ctrl + alt + f1 - f6 : 打开字符终端

用户名:farsight
密码:1

 ctrl + alt +F1:进入界面

ctrl + alt +F5

ctrl + alt +F6

为了多用户使用计算机

结束字符终端: alt + f7

top

top -p PID : 动态查看进程状态

renice -5 PID : 改变进程的NI值(默认0)

8.进程相关函数 

fork/exit       创建进程、退出进程

wait/waitpid ​  回收进程资

8.1 fork(进程创建) 

#include <unistd.h>
#include <sys/types.h> 

pid_t pid; //进程号

pid_t fork(void);
功能:创建进程
参数:无
返回值:
	  pid < 0 :创建进程失败
      pid == 0 : 子进程
      pid > 0 : 父进程  

优缺点
	好处:可以同时处理多项工作,互不干扰
	缺点:进程是资源分配的最小单元,每创建一个进程,虚拟出来4G内存空间,子进程几乎复制了父进程所有的资源
1.进程执行

从fork函数往下分为两个进程开始运行。

父进程和子进程执行顺序是随机的。

父子执行顺序:不确定
若父进程先结束,子进程未结束,子进程就会称为孤儿进程
若子进程先结束,父进程未结束,但父进程没有回收子进程的内核资源,子进程会称为僵尸进程

2.fork函数特性 

1.子进程创建时,几乎拷贝了父进程全部内容,包括代码段、数据段、堆栈段、文件描述符、虚拟地址空间

2.同一个父进程创建的子进程都是属于同一个进程组

   pkill -9 -g PGID

3.进程是管理资源的最小单位  

3.示例 
3.1  fork函数进程执行顺序
1)示例代码 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	pid_t  pid; //进程号
	pid = fork();
	if(pid < 0)
	{
		perror("fork");
		exit(-1);
	}
	else if(pid == 0) //子进程
	{
		while(1)
		{
			printf("son is running!\n");
			sleep(2);
		}
	}
	else{ //父进程
		while(1)
		{
			printf("father is running!\n");
			sleep(2);
		}
	}
	return 0;
}
 2)输出结果

 

8.2 exit(进程主动退出:自带清理缓冲区)

#include <stdlib.h>
void exit(int status); //有缓存
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int fun(void)
{
	printf("aaaaaaa\n");
	exit(0);
    //return 0;   //输出结果:111111  aaaaaaa 222222
}

int main(int argc, const char *argv[])
{
	printf("111111\n");
	fun();
	printf("222222\n");
	return 0;
}
2)输出结果 

 

8.3 _exit(进程主动退出:不清理缓冲区)

//系统调用函数
#include <unistd.h>
void _exit(int status); //无缓存
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int fun(void)
{
	printf("aaaaaaa\n");
	_exit(0);
}

int main(int argc, const char *argv[])
{

	printf("111111\n");
	fun();
	printf("222222\n");
	return 0;
}
2)输出结果

 

8.4 wait(避免僵尸进程:阻塞回收)

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:
    父进程等待子进程结束,回收它的资源
函数参数:
    status指向对象用来保存子进程退出时状态
    status为空,表示忽略子进程退出时状态
    status不为空,表示保存子进程退出时状态
函数返回值
	成功:子进程的进程号
	失败:-1

WEXITSTATUS(status)  获取子进程返回值  
WIFEXITED(status)  判断子进程是否正常结束
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork");
		exit(-1); //return -1
	}
	else if(pid == 0 )//子进程
	{
		int n = 5;
		while(n--)
		{
			printf("son is running!\n");
			sleep(1);
		}
		exit(15);
	}
	else{ //父进程
		int status;
		wait(&status);
		printf("%d\n", WEXITSTATUS(status)); //获取子进程的返回值
		printf("%d\n", WIFEXITED(status)); //判断子进程是否正常结束
		while(1);
	}
	return 0;
}
2)输出结果

子进程先与父进程退出---父进程未回收资源---子进程会变成僵尸进程
        危害:占用进程号、内存空间、PCB进程控制块等
        解决:wait / waitpid / 信号
        注意:任何进程结束都会变成僵尸进程,只是时间有长有短

父进程先与子进程退出---子进程会变成孤儿进程---被init进程接管(收养)

        init进程:系统启动后运行的第一个用户空间进程,pid=1,会定期扫描系统,收养孤儿进程。

        注意:孤儿进程一般没什么危害

8.5 waitpid(避免僵尸进程:非阻塞回收)

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
功能:
    父进程自动回收子进程结束后的资源
参数:
    pid: -1   任意进程
    status: NULL
	options: 
			0 阻塞
			WNOHANG 非阻塞

eg: 
    wait(NULL); 阻塞等待任意一个子进程退出,不接受子进程退出的状态 
    waitpid(-1, NULL, 0); == wait(NULL);
    
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork");
		exit(-1); //return -1
	}
	else if(pid == 0 )//子进程
	{
		int n = 5;
		while(n--)
		{
			printf("son is running!\n");
			sleep(1);
		}
		exit(15);
	}
	else{ //父进程
		//阻塞
		//waitpid(-1, NULL, 0);
		//sleep(15);	
		//非阻塞
		waitpid(-1, NULL, WNOHANG);
		while(1)
		{
			printf("xxxx\n");
			sleep(1);
		}
	}
	return 0;
}
2)输出结果

8.6 vfork()和fork()区别 

fork(): 子进程拷贝父进程的数据段,代码段,且父子进程执行次序不确定
vfork(): 子进程与父进程共享数据段,保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行

        在使用vfork函数在调用exec()或exit()之前,子进程依赖于父进程的进一步动作,将会导致死锁。

8.7 exec函数族

 1.概念

        fork()子进程为了执行新的程序可直接在子进程if中写入新程序代码,但不够灵活

        exec族函数可以把一个编译好的的可执行程序直接加载运行,将子进程单独编译为可执行文件,而将父进程作为主程序并创建子进程用exec来执行已编译好的可执行文件。

        函数族提供了一种在进程中启动另一个程序执行的方法。 它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件:a.out,也可以是Linux下任何可执行脚本文件。

比如bash用到了exec函数来执行我们的可执行文件:ls等

2. 在Linux中使用exec函数族主要有以下两种情况

当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。

如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生

3.函数 
 #include <unistd.h>
     int execl(const char *path, const char *arg, ...);
     int execv(const char *path, char *const argv[]);
     int execlp(const char *file, const char *arg, ...);
     int execvp(const char *file, char *const argv[]);
     int execle(const char *path, const char *arg, ..., char *const envp[]);
     int execve(const char *path, char *const argv[],   char *const envp[]);
  
  
  返回值: 	
  		成功不返回
  		失败返回 -1 更新 errno

注意
exec函数的参数表传递方式以函数名的第五位字母区分:
​        

        字母为"l"(list)的表示逐个列举的方式;

        字母为"v"(vertor)的表示将所有参数构造成指针数组传递;

        以p结尾的函数可以只给出文件名

        以"e"(enviromen)结尾的两个函数execle、execve就可以在envp[]中设置当前进程所使用的环境变量
        使用execleexecve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量

 3.1 execl(逐个列举)
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	
	printf("hello world\n");
	int ret = execl("./test", "test", NULL);
	if(ret == -1)
	{
		perror("execl");
		exit(-1);
	}
	printf("22222222222222\n");
	return 0;
}
2)输出结果 
3.2 execv(所有参数构造成指针数组传递)
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	printf("hello world\n");
	char *arg[] = {"test", NULL};
	int ret = execv("./test", arg);
	if(ret == -1)
	{
		perror("execv");
		exit(-1);
	}
	printf("22222222222222\n");
	return 0;
}
2)输出结果 

 

3.3 execlp(能实现ls -l等快捷命令)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{ 
    printf("hello world\n");
    int ret = execlp("ls","ls","-l",NULL);
    if(ret == -1)
    {
        perror("execl");
        exit(-1);
    }
    printf("02222222\n");
    return 0;
} 
2)输出结果 
3.4 execvp(指针传递)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{ 
    printf("hello world\n");
    char *arg[]={"ls","-l",NULL};
    int ret = execvp("ls",arg);
    if(ret == -1)
    {
        perror("execl");
        exit(-1);
    }
    printf("02222222\n");
    return 0;
} 
2)输出结果 

 3.5 execle(环境变量)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{ 
    printf("hello world\n");
    //设置环境变量
    char *env[]= {"PATH=.",NULL};
    int ret = execle("./test","test",NULL,env);
    if(ret == -1)
    {
        perror("execl");
        exit(-1);
    }
    printf("02222222\n");
    return 0;
} 
2)输出结果 

3.5 execve(环境变量指针传递) 
1)示例代码 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{ 
    printf("hello world\n");

    char *arg[] = {"time",NULL};
    //设置环境变量
    char *env[]= {"PATH=.",NULL};
    int ret = execve("./time",arg,env);
    if(ret == -1)
    {
        perror("execl");
        exit(-1);
    }
    printf("02222222\n");
    return 0;
} 

事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用  

8.8 strtok(将字符串分割成一个片段)

#include <string.h>

char * strtok(char *s, const char *delim);

函数说明:
    strtok()用来将字符串分割成一个个片段. 
    参数s 指向欲分割的字符串;
    参数delim 则为分割字符串,当strtok()在参数s 的字符串中发现到参数delim 的分割字符时则会将该字符改为\0 字符. 
    在第一次调用时,strtok()必需给予参数s 字符串, 往后的调用则将参数s 设置成NULL. 每次调用成功则返回下一个分割后的字符串指针.
参数:
    S:指向欲分割的字符串————》NULL
    delim:为分割字符串
返回值:
    返回下一个分割后的字符串指针, 如果已无从分割则返回NULL.

1)示例代码(实现bash的效果)
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define N 64

int main(int argc, char *argv[])
{
    char buf[N] = {0};   //定义并初始化一个字符串数组
    char *arg[N];     //定义一个指针数组
    pid_t pid;
    int i = 0, j;

    while (1)
    {
        printf("linux@ubuntu:~$");
        fgets(buf, N, stdin);//ls -l -a   //等待用户从键盘输入,回车后将输入的内容写到buf,包括 \n,
										  //此时,buf数组中以\0结尾,\0 前面是 \n,显然 \n 不是命令
        buf[strlen(buf)-1] = '\0';        //将buf数组中的\0前面的那个\n用\0进行覆盖
        if (strcmp(buf, "exit") == 0)     //看输入的是否为exit,注:没有对空格进行处理
            break;

        if ((pid = fork()) == -1)         //创建子进程
        {
            perror("fork");
            exit(-1);
        }
        if (pid == 0)
        {
		/*
			下面是利用strtok函数将buf数值进行解析,把命令和参数分别解析出来,strtok函数的使用参见
			C语言函数大全:下面是部分内容
			头文件:#include <string.h>

			定义函数:char * strtok(char *s, const char *delim);

			函数说明:strtok()用来将字符串分割成一个个片段. 参数s 指向欲分割的字符串, 参数delim 则为分割字符串,当strtok()在参数s 的字符串中发现到参数delim 的分割字符时则会将该字符改为\0 字符. 在第一次调用时,strtok()必需给予参数s 字符串, 往后的调用则将参数s 设置成NULL. 每次调用成功则返回下一个分割后的字符串指针.

			返回值:返回下一个分割后的字符串指针, 如果已无从分割则返回NULL.

			范例
			#include <string.h>
			#include <stdio.h>
			int main(void)
			{
				char s[] = "ab-cd : ef;gh :i-jkl;mnop;qrs-tu: vwx-y;z";
				char *delim = "-: ";
				char *p;
				printf("%s ", strtok(s, delim));
				while((p = strtok(NULL, delim)))
					printf("%s ", p);
				printf("\n");
				return 0;
			}
			执行结果:
			ab cd ef;gh i jkl;mnop;qrs tu vwx y;z //-与:字符已经被\0 字符取代

		*/
            arg[i] = strtok(buf, " ");      
            do
            {
                ++i;
                arg[i] = strtok(NULL, " ");
            }while(arg[i] != NULL);

            for (j = 0; j < i; j++)
                printf("%s\n", arg[j]);

        //    char * arg[] = {"ls", "-l", "-a", NULL};
            if (-1 == execvp(arg[0], arg)) //在子进程中调用execvp函数,子进程的三段被替换
            {
                perror("execvp");
                exit(-1);
            }
        }
        else
            wait(NULL);  //父进程等待子进程就是,即等待argv[0]中指向的命令执行完
    }
    return 0;
}
2)输出结果

 

9.daemon(守护进程) 

9.1 什么是守护进程

守护进程即为Daemon进程,是linux三种进程之一,在系统启动时运行,关闭时结束;在linux中与用户交互的界面叫终端,从终端运行起来的程序都依附于这个终端,当终端关关闭时,相应的进程都会被关闭,守护进程可以突破这个限制。

特点:

  • 始终运行在后台,后台服务进程
  • 独立于任何终端(和终端无关)比如:init进程 pid=1 开机运行 关机才结束
  • 周期性地执行某种任务或等待处理特事件

9.2 什么是会话? 

a.Linux是以会话(session),进程组的方式管理进程.每个进程属于一个进程组,子进程同属于该进程组.
b.会话是由一个或者多个进程组的集合,通常用户打开一个终端,系统就会创建一个会话,所有通过该终端运行都属于这个会话,shell进程--->会话组的组长,一个会话最多打开一个控制终端,当控制终端结束时,所有的进程也跟着结束。

9.3 守护进程创建流程 

1. 创建子进程,父进程退出,fork() 为了成为叫后台进程
    fork(void);
2. 在子进程中创建新会话,(脱离原来的会话,成为新的会话组长)
    #include <sys/types.h>
    #include <unistd.h>
    pid_t setsid(void);
3. 修改工作目录
    #include <unistd.h>
    int chdir(const char *path);
4. 修改umask (增加安全性)
    #include <sys/types.h>
    #include <sys/stat.h>
    mode_t umask(mode_t mask); 
5. 关闭文件描述(回收资源)
    #include <unistd.h>
    close();
		

9.4 示例代码 

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

int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork");
		exit(-1);
	}
	else if(pid == 0)
	{
		setsid(); //2.开启新的会话
		chdir("/tmp"); //3.改变工作目录
		umask(0);  //4.增加安全性
		close(0);
		close(1);
		close(2); //5.关闭文件描述符
		FILE *fp;
		while(1)
		{
			fp = fopen("/tmp/test.txt", "a");
			time_t t;
			time(&t);
			fprintf(fp, "%s", ctime(&t));
			fflush(fp);
			sleep(1);
		}
	}
	else{  //父进程
		exit(0); //1.父进程退出
	}

	return 0;
}

五、线程

1.学线程的优点

1) 由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大

2) 为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程   

3) 在同一个进程中创建的线程共享该进程的地址空间(除了栈区之外)

2.线程的概念 

  1. 每个用户进程有自己的虚拟地址空间

  2. 系统为每个用户进程创建一个 task_struct 来描述该进程 struct task_struct

  3. task_struct 和地址空间映射表一起用来表示一个进程

  4. 由于进程的虚拟地址空间是私有的,因此进程切换开销很大

  5. 为了提高系统的性能,linux引入轻量级进程, 起名为线程

  6. 在同一个进程中创建的线程共享该进程的地址空间

  7. Linux里同样用task_struct来描述一个线程。

线程和进程都参与统一的调度

总结:

  1. 通常线程指的是共享相同虚拟地址空间的多个任务

  2. 使用多线程, 大大提高了任务切换的效率

线程不需要虚拟内存,为什么?

        每个进程中至少有一个线程,就是主线程,还可以产生多个线程

共享4G内存空间,线程切换只需要虚拟CPU(寄存器)

同样用task_struct来描述一个线程,线程和进程都参于统一的调度

进程代表资源分配的最小单位

线程是最小的调度单位

同一个地址空间中的多个任务

3.线程与进程的区别 

线程和进程是操作系统中用于并发执行的两个基本概念,它们之间有着本质的区别:
1. 进程(Process)
   - 进程是操作系统进行资源分配和调度的基本单位。它是一个具有独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度的一个独立单位
   - 每个进程都拥有独立的地址空间,一个进程崩溃后,在保护模式下不会影响到其他进程,因为系统为每个进程提供了独立的内存空间。
   - 进程间的通信(IPC,Inter-Process Communication)需要依赖特定的机制(例如管道、消息队列、信号量、共享内存等)。
2. 线程(Thread)
   - 线程是进程的执行单元,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,并且线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可以与同属一个进程的其他线程共享进程所拥有的全部资源。
   - 一个进程可以有多个线程,同一个进程中的线程间可以直接通信
   - 线程有时被称为轻量级进程(Lightweight Process),它们的创建、撤销和切换比进程更快。
        在多线程环境中,一个进程内的多个线程可以共享进程资源,如内存堆、打开的文件等,但每个线程有自己的执行堆栈和程序计数器等少量资源。线程间的通信比进程间通信要简单和高效,因为它们不需要操作系统介入,只需要在进程的地址空间内进行数据交换即可。
        多线程设计可以让程序更有效地利用多核处理器的计算资源,提高程序的响应速度和性能,特别是在用户界面程序和服务器程序中应用广泛。然而,多线程也引入了诸如线程安全、死锁、竞态条件等复杂性问题,需要开发者谨慎设计并使用同步机制来避免。 

4.线程的相关函数 

4.1 pthread_create(创建线程)

#include <pthread.h>

pthread_t tid;  //线程号

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建线程

参数:
  	thread: &tid  线程号的地址
	attr: NULL  默认参数(缺省参数) 线程的属性
    start_routine:  线程函数的函数名   
    arg: 给线程函数传递的参数 如果不传参 写NULL

返回值:
	成功: 0
	失败: 错误码 -1

Compile and link with -pthread.
使用-pthread编译和链接。

1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

//线程函数
void *fun(void * arg)
{
    while(1)
    {
        printf("fun is running\n");
        sleep(2);
    }
}
int main(int argc, char *argv[])
{ 
    pthread_t tid;  //线程号
    int ret = pthread_create((&tid),NULL,fun,NULL);
    if(ret == -1)
    {
        perror("pthread_create");
        return -1;
    }
    while(1)
    {
        printf("tid is running\n");
        sleep(2);
    }
    return 0;
} 
2)输出结果 

4.2  pthread_join(回收线程资源:阻塞)

int pthread_join(pthread_t thread, void **retval);
功能:主线程等待子线程结束,回收它的资源(阻塞)
  
参数:
     thread: 线程号
     retval: 子线程的返回值
返回值:
	成功:0
    失败:-1

a.函数在线程退出时,用来清理线程资源。
b.会阻塞调用方,直至pthread_join所指的线程退出
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

//线程号
void *fun1(void * arg)
{
    int n =5;

    while(n--)
    {
        printf("fun is running\n");
        sleep(1);
    }
}
int main(int argc, char *argv[])
{ 
    pthread_t tid;//线程号
    int ret = pthread_create((&tid),NULL,fun1,NULL);
    if(ret == -1)
    {
        perror("pthread_create");
        return -1;
    }
    printf("aaaaaaaaaaaa\n");
    pthread_join(tid,NULL);
    printf("bbbbbbbbbbbbb\n");
    while(1);
    return 0;
} 
2)输出结果

4.3 pthread_detach(回收线程资源:非阻塞) 

int pthread_detach(pthread_t thread);  
功能:主线程和子线程分离开来,自动回收资源 (非阻塞)
  
参数:
  	thread: 线程号
  
返回值:
	  成功:0
      失败:-1  

a.因为线程默认的状态是结合态的,所以,可以通过pthread_detach函数来设置线程为分离态。
b.使用pthread_detach函数后,使线程处于分离态;
c.使用pthread_detach函数后,线程在退出后,会自己清理资源
d.相较pthread_join,使用pthread_detach函数不会阻塞主线程,但是无法获取线程的返回值。
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

//线程函数
void *fun(void *arg)
{
	int n = 5;
	while(n--)
	{
		printf("fun is running!\n");
		sleep(1);
	}
}

void *fun1(void *arg)
{
	int n = 8;
	while(n--)
	{
		printf("fun1 is running!\n");
		sleep(1);
	}
}

void *fun2(void *arg)
{
	int n = 12;
	while(n--)
	{
		printf("fun2 is running!\n");
		sleep(1);
	}
}

int main(int argc, const char *argv[])
{
	pthread_t tid, tid1, tid2; //线程号
	int ret = pthread_create(&tid, NULL, fun, NULL);
	if(ret == -1)
	{
		perror("pthread_create");
		return -1;
	}

	printf("aaaaaaaaaaa\n");

	//pthread_join(tid, NULL);
	pthread_detach(tid);

	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_detach(tid1);

	pthread_create(&tid2, NULL, fun2, NULL);
	pthread_detach(tid1);

	printf("bbbbbbbbbbbb\n");
	while(1);
	return 0;
}
2)输出结果 

4.4  pthread_exit(线程的退出:主动退出)

#include <pthread.h>

void pthread_exit(void *retval);
功能:结束子线程
参数:
      retval : void * 线程退出返回的值
返回值:
	  成功:0
	  失败:-1

1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
//线程函数
void *fun(void * arg)
{
    char *p  = (char *)arg;
    printf("%s\n",p);
    pthread_exit(arg);//相当于return arg;
}
int main(int argc, char *argv[])
{ 
    pthread_t tid;//线程号
    char buf[100]= "hello";
    pthread_create(&tid,NULL,fun,(void *)buf);
    void *arg;
    pthread_join(tid,&arg);
    printf("%s %s\n",buf,(char *)arg);

    while(1)
    return 0;
} 
2)输出结果 

4.5 pthread_cancel(线程的退出:被动退出)

#include <pthread.h>

int pthread_cancel(pthread_t thead)
函数参数:
    thread:要取消的线程
返回值:
    成功:0 
    出错:-1
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void * arg)
{
    while(1)
    {
        printf("fun is running\n");
        sleep(1);
    }
}
int main(int argc, char *argv[])
{ 
    pthread_t tid;
    char buf[100] = {0};
    pthread_create((&tid),NULL,fun,NULL);
    pthread_detach(tid);

    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        if(strncmp(buf,"q",1) == 0)
        {
            pthread_cancel(tid);
        }
        if(strncmp(buf,"Q",1) == 0)
        {
            exit(0);
        }
    }
    
} 
2)输出结果

4.6 练习

创建子线程一:模拟 cat filename   并返回执行情况 ,成功返回OK,失败返回ERROR
创建子线程二:模拟 ls pathname  并返回执行情况 , 成功返回OK,失败返回ERROR

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//创建线程的函数: pthread_create
//等待线程的结束的函数 pthread_join  pthread_detach()
//线程的退出:   pthread_exit() pthread_cancel()

//线程一:模拟 cat filename
//线程二:模拟 ls pathname

void * mycat(void *arg)
{
	//标准IO:fopen fclose fread fwrite fgetc fputc fgets fputs fprintf fscanf feof fseek ftell 
	//文件IO: 
     //文件:open close read write lseek 
	 //目录: opendir closedir readdir stat(ls -l) 获得文件的属性
	//&fd = arg;将主线程的&fd给arg
	//通过arg拿到原来的fd
	//线程1:模拟cat filename
	//将你打开文件描述符传递给我
	int *pfd = (int *)arg;
	int fd =*pfd;
	printf("thread1 fd = %d\n",fd);

ls show ok****
	//读取fd指向文件中的内容
	char buf[20]={'\0'};
	printf("-------------------pthread1---------------\n");
	while(1)
	{
		memset(buf,'\0',sizeof(buf));
		//read() 参数1:文件描述符  参数2:缓冲区的地址  参数3:读取的字节数
		int n=read(fd,buf,sizeof(buf)-1);
		if(n==0)  
		{
			printf("---------------------\n");
			pthread_exit("file read over");
		}
		else if (n<0){
			pthread_exit("file read error");
		}
		printf("%s",buf);
	}



}
void * myls(void *arg)
ls show ok****
{
	//线程2:模拟 ls pathname
	//将打开文件夹描述符传递给我
	DIR *pDir = (DIR *)arg;
	printf("thread2 pDir = %p\n",pDir);
	if(NULL == pDir)
	{
		pthread_exit("pathname no exit");//子线程给主线程传递
	}
	struct dirent * pd = NULL;
	printf("**********pthread 2*************************\n");
	while(1)
	{
		pd = readdir(pDir);
		if(NULL == pd)
		{
			printf("\n**************************************\n");
			pthread_exit("ls show ok");
		}
		printf("%s	",pd->d_name);//显示文件名
	}
}
int main(int argc, const char *argv[])
{
	pthread_t pth1,pth2;
	
	//2.以只读方式
	
	int fd = open("1.txt",O_RDONLY);
	printf("main fd = %d\n",fd);
	//打开目录
	DIR *pDir = opendir("./");
	printf("main pdir = %p\n",pDir);
	//创建线程1,模拟cat filename
	//参数4:void * 任何数据类型的地址
	pthread_create(&pth1,NULL,mycat,&fd);//主线程中的值传递给子进程
	//创建线程2,模拟ls pathname
	pthread_create(&pth2,NULL,myls,pDir);
	//接受子线程传过来的值
	char *result = NULL;
	pthread_join(pth1,(void**)&result);
	printf("pth1 result=%s****\n",result);
	pthread_join(pth2,(void **)&result);
	printf("pth2 result=%s****\n",result);
	return 0;
}

优点:线程间很容易进行通信 通过全局变量实现数据共享和交换

缺点:多个线程同时访问共享对象时需要引入同步和互斥机制

 六、同步和互斥

同步和互斥 :保护共享资源,避免竟态

同步:多个任务按理想的顺序/步调来进行

互斥:不能同时访问

都是为了避免竟态:多个任务同时访问共享资源

1.同步 

1.1 什么是同步?

多线程访问临界资源,按照某种顺序执行
        无名信号量:用于线程间的同步
        有名信号量:用于进程间的同步----信号灯集

1.2 线程的同步---信号量 

1.概念 

a.信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问.
b.当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现他们之间的顺序执行

2.sem_init/wait/post
#include <semaphore.h>

sem_t sem; //信号量的变量

int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
    sem: &sem  信号量的变量的地址
	pshared: 0 :线程   1 :进程
    value: 信号量的初值
返回值:
	成功:0
    失败:-1

int sem_wait(sem_t *sem);  //-1
//如果初值为0,会阻塞
参数:
    sem:信号量
		当信号量值>0    会将信号量的值减1
		当信号量值==0   阻塞线程
返回值:
    成功:0 
    出错:-1


int sem_post(sem_t *sem);  //+1
参数:
    sem:信号量
		将信号量的值加1,同时唤醒等待的线程
返回值:
    成功:0 
    出错:-1
1)示例代码(主线程等待子线程结束)
/*===============================================
*   描    述:主线程等待子线程结束
================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

sem_t sem; //信号量

void *fun(void * arg)
{
    int n = 5;
    while(n--)
    {
        printf("fun is running\n");
        sleep(1);
    }
    sem_post(&sem); //+1
}
int main(int argc, char *argv[])
{ 
    sem_init(&sem,0,0);
    pthread_t tid;
    pthread_create(&tid,NULL,fun,NULL);
    pthread_detach(tid);

    printf("111111\n");
    sem_wait(&sem); //-1
    printf("222222\n");
    while(1);
    
    return 0;
} 
2)输出结果 

 3.实践练习(计算键盘输入字符串的个数

buf: 全局变量

主线程:输入字符串

子线程:打印字符串的长度  

1)示例代码
/*===============================================
*   描    述:计算键盘输入字符串的个数
================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

sem_t sem; //信号量
char buf[100] = {0};
void *fun(void * arg)
{
  /*  int sum = 0;
    for(int i = 0;buf[i];i++)
    {
       sum += buf[i];
    }
    printf("%d\n",sum);
    sem_post(&sem); //+1*/
    while(1)
    {
        sem_wait(&sem);
        printf("%ld\n",strlen(buf)-1);
    }
}
int main(int argc, char *argv[])
{ 
    sem_init(&sem,0,0);
    pthread_t tid;
    pthread_create(&tid,NULL,fun,NULL);
    pthread_detach(tid);

    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        sem_post(&sem);

    }
    
    return 0;
} 
2)输出结果 

4.实践练习(用多线程进行读写操作:文件拷贝) 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

sem_t sem1,sem2;
char buf[100] = {0};
int flag = 0;
int n = 0;

void *read_data(void *arg)
{
	//先读 从1.txt读取
	int fd = open("./1.txt", O_RDONLY);
	
	while(1)
	{
		sem_wait(&sem2);   //灯B -1
	//	printf("111111111111\n");
	//	sleep(1);
	
		n = read(fd, buf, sizeof(buf));
		if(n < 0)
		{
			perror("read");
			return NULL;
		}
		else if(n == 0)
		{
			flag = 1;
			close(fd);
			break;
		}	

		sem_post(&sem1);   //灯A +1
	}

}

void *write_data(void *arg)
{
	//再写到2.txt
	int fd = open("./2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

	while(1)
	{
		sem_wait(&sem1);   //灯A -1
		//printf("22222222222222\n");
		//sleep(1);
		
		write(fd, buf, n);
		
		if(flag == 1)
		{
			close(fd);
			break;
		}

		sem_post(&sem2);   //灯B +1
	}
}

int main(int argc, const char *argv[])
{
	sem_init(&sem1, 0, 0);  //灯A
	sem_init(&sem2, 0, 1);  //灯B

	pthread_t tid1,tid2;
	pthread_create(&tid1, NULL, read_data, NULL);
	pthread_detach(tid1);

	pthread_create(&tid2, NULL, write_data, NULL);
	pthread_detach(tid2);

	while(1);
	return 0;
}

2.互斥 

2.1 什么是互斥? 

多线程访问临界资源时,没有顺序而言,若线程A访问临界资源,不允许其他线程访问资源。
引入互斥锁:用来保证共享数据操作的完整性。

2.2 线程的互斥---互斥锁 

1. pthread_mutex_init/lock/unlock
1.定义互斥锁 (全局)
pthread_mutex_t  myMutex;  //互斥锁的变量
2.初始化锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t   *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化互斥锁
参数:
	mutex: &mutex互斥锁
	mutexattr:互斥锁属性(NULL表示缺省属性)                                          	
返回值:
    成功:0 
    出错:-1
3.上锁(申请互斥锁)
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex)
参数:
    mutex:互斥锁    
返回值:
    成功:0 
    出错:-1
4.解锁
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex) 
参数:
    mutex:互斥锁    
返回值:
    成功:0 
    出错:-1
1)示例代码 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

int i, value1, value2;

pthread_mutex_t mutex; //互斥锁的变量

void *fun1(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        i++;
        value1 = i;
        value2 = i;
        pthread_mutex_unlock(&mutex);
    }

}

void *fun2(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(value1 != value2)
        {
            printf("%d %d %d\n",i, value1, value2);		
        }
        pthread_mutex_unlock(&mutex);
    }
}

int main(int argc, const char *argv[])
{
    //初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    pthread_t tid1,tid2;
    pthread_create(&tid1, NULL, fun1, NULL);
    pthread_detach(tid1);

    pthread_create(&tid2, NULL, fun2, NULL);
    pthread_detach(tid2);

    while(1);
    return 0;
}
2.实践练习(用多线程进行读写操作:文件拷贝
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

char buf[100] = {0};
int fd1,fd2,n=0;
pthread_mutex_t mutex; //互斥锁的变量

void *myread(void *arg)
{
    fd1 = open( "./1.txt" , O_RDONLY);
    while(1)
    {
        pthread_mutex_lock(&mutex);
        n = read(fd1, buf, sizeof(buf));
        sleep(1);
        if(n== 0)
        {
            perror("read");
            break;
        }
        pthread_mutex_unlock(&mutex);
    }

}

void *mywirte(void *arg)
{
    fd2 = open( "./2.txt" , O_WRONLY|O_CREAT|O_TRUNC, 0666);
    while(1)
    {
        write(fd2,buf,n);
        sleep(1);
    }

}

int main(int argc, const char *argv[])
{
    //初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    pthread_t tid1,tid2;

    pthread_create(&tid1, NULL, myread, NULL);
    pthread_detach(tid1);

    pthread_create(&tid2, NULL, mywirte, NULL);
    pthread_detach(tid2);

    pthread_join(tid2,NULL);
    while(1);
    return 0;
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
pthread_mutex_t mutex;
char buf[1024] = {0};
int m = 0;
void *mywirte(void *arg)
{
    int fp2 = open("./2.txt",O_WRONLY|O_CREAT|O_TRUNC,0777);
    while(1)
    {
        write(fp2,buf,m);
        sleep(1);
    }
}
void *myread(void *arg)
{
    int fp1 = open("./1.txt",O_RDONLY|O_CREAT,0777);
    while(1)
    {
        pthread_mutex_lock(&mutex);
        m = read(fp1,buf,sizeof(buf));
        sleep(1);
        if(m == 0)
        {
            printf("readover\n");
            break;
        }
        pthread_mutex_unlock(&mutex);
    }
}
int main(int argc, char *argv[])
{ 
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,mywirte,NULL);
    pthread_detach(tid1);
    pthread_create(&tid2,NULL,myread,NULL);
    pthread_detach(tid2);
    pthread_mutex_init(&mutex,NULL);
    //close(fp1);
    //close(fp2);
    while(1);
    return 0;
} 

七、进程间通信 

 传统进程间通信方式:

        无名管道、有名管道、信号

system V的 IPC对象:

        消息队列、共享内存、信号灯集(信号量)

BSD:

        套接字

进程间通信(Inter-Process Communication,IPC)是操作系统中用于不同进程之间传递数据和同步状态的一种机制。进程间通信的方式主要有以下几种:
1. 管道(Pipe):管道是一种单向数据流通信机制,可以看作是进程间的单向数据通道。它允许一个进程向另一个进程发送数据,但不能反向通信。管道分为匿名管道和命名管道两种。
2. 命名管道(无名管道)(Named Pipe):命名管道也称为FIFO,它是一种特殊的文件,可以在任意两个进程之间建立通信通道。命名管道的通信效率比匿名管道要高,因为它避免了在内核中为每个管道创建数据结构。
3. 消息队列(Message Queue):消息队列是一种先进先出(FIFO)的数据结构,用于进程间的消息传递。发送进程将消息放入队列中,接收进程从队列中读取消息。消息队列可以实现多个进程之间的通信。
4. 信号(Signal):信号是一种简单的通信方式,用于通知接收进程某个事件已经发生。信号处理函数可以用来处理信号,从而实现进程间的异步通知。
5. 共享内存(Shared Memory):共享内存允许两个或多个进程访问同一块内存空间,从而实现数据共享。共享内存的通信效率最高,但需要进程间同步,以避免数据竞争和冲突。
6. 套接字(Socket):套接字是一种基于网络的通信机制,可以实现不同主机上的进程间通信,也可以用于同一主机上的进程间通信。套接字支持面向连接和无连接两种通信方式,以及可靠的传输。
7. 信号量(Semaphore):信号量是一种用于进程间同步的机制,可以保证多个进程访问共享资源的互斥和同步。信号量可以用于控制对共享资源的访问,以避免竞争和冲突。
 

1.无名管道(pipe)

查看命令: man 2 pipe

头文件:
#include <unistd.h>

函数原型: 
int pipe(int pipefd[2]);

参数:
    pipefd[2] :无名管道的两个文件描述符,int型的数组,大小为2,pipefd[0]为读端,pipefd[1]为写端

返回值:
	成功:0
	失败:-1

1.1 无名管道的特点 

a、没有名字,因此无法使用open()打开

b、只能用于亲缘进程间(如父子进程、兄弟进程、祖孙进程等)通信

c、半双工工作方式,读写端是分开的,pipefd[0]为读端,pipefd[1]为写端

d、是一种特殊的文件,只存在内存中,由内核进行管理

e、对于它的读写可以使用文件IO如read、write函数

f、无名管道的操作属于一次性操作,如果对无名管道进行读操作,数据会被全部读走

单工:固定一种方向进行通信  广播

半双工:同一时间只能由一端发送到另一端  对讲机

全双工:通信方向都可以,同时可以发送也可以接收  电话

        既然说是管道,所以可以想象成一条水管,连接两个进程, 一个进程负责输入数据,另一个进程负责接收数据,反过来也一样。

        所以在无名管道中也一样,无名管道的两端,每一端都可以读和写

调用fork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])。 

 

1.2 示例:一个进程 

1)fork进程代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(-1);
    } else if (pid == 0) {
        // 子进程写数据
        int fd = open("./1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
        write(fd, "hello\n", 7);
        close(fd);
        _exit(0);  // 子进程结束后退出
    } else {
        int status;
        waitpid(pid, &status, 0);  // 等待子进程结束
        sleep(1);  // 等待子进程写入完成
        
        // 父进程读数据
        int fd = open("./1.txt", O_RDONLY);
        char buf[100] = {0};
        read(fd, buf, sizeof(buf));
        printf("%s\n", buf);
        close(fd);
    }
    return 0;
}

 

2)pipe进程代码
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1)
    {
        perror("pipe");
        return -1;
    }
    write(fd[1],"hello world",12);
    char buf[100] = {0};
    int n =read(fd[0],buf,sizeof(buf));
    printf("n = %d\n buf =%s\n",n,buf);

    return 0;
} 

 

1.3 示例:两个亲缘进程  

 若一端为读就要关闭他的写功能,另一端就只能为写关闭读功能。

1)亲缘进程代码
/*===============================================
*   描    述:亲缘进程
================================================*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{ 
    int fd[2];//两个管道,fd[0]读端,fd[1]写端
    int ret = pipe(fd);//创建无名管道
    if(ret == -1)//判断管道读写是否成功
    {
        perror("pipe");
        return -1;
    }

    pid_t pid = fork();//创建进程
    if(pid < 0)//判断进程是否创建成功
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)//子进程
    {
        close(fd[0]);//关闭读端
        char buf[100] = {0};
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);//从键盘上输入内容
            write(fd[1],buf,strlen(buf));//进行管道写
        }
    }
    else 
    {
        close(fd[1]);//关闭写
        char buf[100] = {0};
        while(1)
        {
            read(fd[0],buf,sizeof(buf));//进行读操作
            printf("%s\n",buf);//打印相关信息
            memset(buf,0,100);//对空间进行释放
        }
    }
    return 0;
} 

 

1.4 实现本地聊天软件

1)user1(用户1)
/*===============================================
 *   描    述:本地聊天软件(user1)
 ================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

char buf[100] = {0};
void *write_data(void *arg)
{
    int fd = open("./f1", O_WRONLY);

    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        write(fd, buf, strlen(buf));
    }

}

void *read_data(void *arg)
{
    int fd = open("./f2", O_RDONLY);
    while(1)
    {
        read(fd, buf, sizeof(buf));
        printf("buf=%s\n", buf);
        memset(buf, 0, 100);
    }
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,read_data,NULL);
    pthread_detach(tid1);

    pthread_create(&tid2,NULL,write_data,NULL);
    pthread_detach(tid2);

    while(1);    
    return 0;
}
2)user2(用户2)
/*===============================================
 *   描    述:本地聊天软件(user2)
 ================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

char buf[100] = {0};
void *write_data(void *arg)
{
    int fd = open("./f2", O_WRONLY);
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);

        write(fd, buf, strlen(buf));
    }

}

void *read_data(void *arg)
{
    int fd = open("./f1", O_RDONLY);
    while(1)
    {
        read(fd, buf, sizeof(buf));
        printf("buf=%s\n", buf);
        memset(buf, 0, 100);
    }
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,read_data,NULL);
    pthread_detach(tid1);

    pthread_create(&tid2,NULL,write_data,NULL);
    pthread_detach(tid2);

    while(1);
    
    return 0;
}

1.5 其他实现

1)当管道中无数据时,执行读数据,读操作堵塞 
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{ 
    int fd[2];
    pipe(fd);
    printf("11111111\n");
   // write(fd[1],"hello\n",7);
    char buf[100] = {0};
    read(fd[0],buf,sizeof(buf));
    printf("buf = %s\n",buf);
    printf("222222222\n");
    return 0;
} 

 

2) 求管道大小
第一种 
/*===============================================
*   描    述:管道大小
================================================*/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{ 
    int fd[2];
    pipe(fd);
    char ch = 'x';
    int i;
    for(i = 0;i < 1024*64-1;i++)
    {
        write(fd[1],&ch,1);
    }
    printf("1111111111\n");
    write(fd[1],&ch,1);
    printf("2222222222\n");
    return 0;
} 

 

第二种 
/*===============================================
*   描    述:求管道大小
================================================*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{ 
    int fd[2];
    pipe(fd);

    char buf[4096] = {0};
    for(int i=0;i<sizeof(buf);i++)
    {
        buf[i] = 'a';
    }
    for(int j = 0; ; j++)
    {
        write(fd[1],buf,sizeof(buf));
        printf("%d\n",j);
    }
    close(fd[0]);
    close(fd[1]);
    return 0;
} 

 

第三种
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>
int main()
{
    int fd[2];
    if(pipe(fd) < 0)
    {
        perror("pipe");
        return -1;
    }
    int ret;
    int size = 0;
    int flags = fcntl(fd[1], F_GETFL);
    fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); // 设置为非阻塞
    while (1) 
    {
        ret = write(fd[1], "c", 1); 
        if (ret < 0)
        {
            perror("write"); 
            break;
        }
        size++;
    }
    printf("size=%d\n", size);
    return 0; 
}

 

3)pipe_queue(队列) 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd[2];
	pipe(fd);
	write(fd[1], "xxx", 3);
	write(fd[1], "yyy", 3);
	write(fd[1], "hello", 5);
	char buf[100] = {0};
	read(fd[0], buf, 4);
	printf("1 = %s\n", buf);
	memset(buf, 0, 100);
	read(fd[0], buf, 4);
	printf("2 = %s\n", buf);
	memset(buf, 0, 100);
	return 0;
}

 

4)pipe_singal
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd[2];

	pipe(fd);
	close(fd[0]);
	write(fd[1], "xx", 2);
	printf("-------------\n");

	return 0;
}
5) pipe_wnohang
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd[2];
	pipe(fd);
	write(fd[1], "hello", 5);
	close(fd[1]);
	char buf[100] ={0};
	int n1 = read(fd[0], buf, 5);
	printf("-------n1 = %d------\n", n1);
	int n2 = read(fd[0], buf, 5);
	printf("-------n2 = %d------\n", n2);

	return 0;
}

1.6 无名管道需要注意的问题 

  1. 当管道中无数据时,执行读操作,读操作阻塞

  2. 无名管道的大小是固定的,管道一旦满,写操作就会导致进程阻塞

  3. 对无名管道的操作,类似一个队列,后写入的数据不会覆盖之前的数据,会在其后面存储,读取完的数据会从管道里面移除

  4. 将读端关闭,向无名管道中写数据,管道破裂,进程收到信号(SIGPIPE),默认这个信号会将进程杀死

  5. 当管道中有数据,将写端关闭,读操作可以执行,之后数据读完,可以继续读取(非阻塞),直接返回0

 2.有名管道(fifo)

查看命令:man 3 mkfifo

1、查看命令:man 3 mkfifo

2、头文件:
#include <sys/types.h>
#include <sys/stat.h>

3、函数原型:
int mkfifo(const char *pathname, mode_t mode);
功能: 创建有名管道
参数:
  pathname: 文件的路径名
  mode: 创建的权限(0644)
  

在shell中使用mkfifo命令: mkfifo filename

eg:
    mkfifo f1

or

if(mkfifo("fifo",0666) == -1)
{  
		perror("mkfifo ");
		return -1;
}

2.1 有名管道的特点 

有名管道也叫命名管道,在文件系统目录中存在一个管道文件。

管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。

特点:

 1. 有名管道存在文件系统中,数据存在内存中
 2. 可以用于无亲缘关系的进程
 3. 只有读端和写端同时存在管道才能打开成功。

2.2 数据传输特点 

1.读端不存在时,写端写入数据将会阻塞

2.读端意外结束,写端再写数据将会管道破裂,该进程结束

3.有名管道的数据存储在内存中,数据交互在内核中  

 2.3 示例:fifo_weite

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

int main(int argc, char *argv[])
{ 
        //写
        char buf[100] = {0};
        int fp = open("./f1",O_WRONLY);
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);
            write(fp,buf,strlen(buf));
        }

    close(fp);

    return 0;
} 

2.4 示例:fifo_read


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

int main(int argc, const char *argv[])
{
	//读
	int fd = open("./f1", O_RDONLY);

	char buf[100] = {0};

	while(1)
	{
		read(fd, buf, sizeof(buf));
		printf("buf = %s\n", buf);
		memset(buf, 0, 100);
	}

	return 0;
}

 2.5 综合(fifo)

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

int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork");
		return -1;
	}
	else if(pid == 0)
	{
		//写
		int fd = open("./f1", O_WRONLY);
		char buf[100] = {0};
		while(1)
		{
			gets(buf);
			write(fd, buf, strlen(buf));
		}
	
	}
	else{
	
		//读
		int fd = open("./f1", O_RDONLY);
		char buf[100] = {0};

		while(1)
		{
			read(fd, buf, sizeof(buf));
			printf("buf = %s\n", buf);
			memset(buf, 0, 100);
		}
	
	}

	return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

char buf[100] = {0};
void *write_data(void *arg)
{
    int fd = open("./f2", O_WRONLY);
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        write(fd, buf, strlen(buf));
    }

}

void *read_data(void *arg)
{
    int fd = open("./f1", O_RDONLY);
    while(1)
    {
        read(fd, buf, sizeof(buf));
        printf("buf=%s\n", buf);
        memset(buf, 0, 100);
    }
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,read_data,NULL);
    pthread_detach(tid1);

    pthread_create(&tid2,NULL,write_data,NULL);
    pthread_detach(tid2);

    while(1);
    
    return 0;
}

 2.6 有名管道和无名管道的异同点

1.相同点

        open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。

2.区别

        有名在任意进程之间使用,无名在父子进程之间使用

 3.信号

简单概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

kill把信号发送给进程或进程组;

raise把信号发送给(进程)自身

 

硬件产生:

SIGINT : ctrl + c  终止前台进程

SIGQUIT : ctrl +\  终止进程

SIGTSTP : ctrl + z 停止进程

软件产生:

        raise()函数: raise函数允许进程向自己发送信号

 3.1 Kill()和raise()(信号发送)

#include <signal.h>

int kill(pid_t pid, int  signo);
功能:
    可以终止进程,也可以向进程发送其他信号
参数:
    pid:
        正数:发送信号给进程号为pid的进程
        0:信号被发送到所有和当前进程在同一个进程组的进程
        -1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
        <-1:信号发送给进程组号为-pid的每一个进程
    signo:信号类型
函数返回值:
    成功:0
    失败:-1

int raise(int  signo);
功能:
    只允许进程向自身发送信号
参数:
    signo:信号类型
函数返回值:
    成功:0
    失败:-1


raise(signo); ====== kill(getpid(), signo);
1)示例代码 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char *argv[])
{ 
    // while(1);
    sleep(5);
    //杀死自己
    //kill(getpid(),SIGKILL);
   // raise(SIGKILL);
    //杀死父进程
    kill(getppid(),SIGKILL);
    printf("---------------\n");
    return 0;
} 

 3.2 alarm()和pause()(定时器信号)

1.alarm(设置闹钟)
#include <unistd.h>

unsigned int alarm(unsigned int seconds);
函数传入值:
        seconds:指定秒数,系统经过seconds秒之后向该进程发送SIGALARM信号
函数返回值:
        成功:如果调用此函数前,进程中已经设置了闹钟时间,
             则返回上一个闹钟剩余时间,否则返回0
        失败:-1


可以为当前进程定义闹钟,时间到了会发出SIGALRM信号。 
每个进程只能有一个alarm,当重新定义时,会重新计时。 
如果之前定义了一个闹钟,则这次定义返回的是上次闹钟剩余的时间,否则返回0.  
1)示例代码 
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    alarm(5);  //定时5秒
    sleep(2); //先运行2秒
    int ret = alarm(5);//从上一个闹钟
    printf("ret=%d\n",ret);
    while(1);

    return 0;
} 
2)输出结果

2. pause(程序暂停)
int pause(void);

pause函数的作用,是让当前进程暂停运行,交出CPU给其他进程去执行;
当前进程进入pause状态后,当前进程会表现为“卡住、阻塞住”;
要退出pause状态,当前进程需要被信号唤醒。
 1)示例代码
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    alarm(5);  //定时5秒
    sleep(2); //先运行2秒
    pause();  //程序暂停
    int ret = alarm(5);//从上一个闹钟
    printf("ret=%d\n",ret);
    while(1);

    return 0;
} 
 2)输出结果

 

3.3 signal()和sigaction()(信号的设置) 

信号的三种处理方式:
1.忽略  2.默认  3.自定义信号处理函数 

1. signal(设置信号的处理方式)
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
    捕获信号,设置信号的处理方式
参数:
    signum:指定信号代码
    handler: SIG_IGN:忽略
             SIG_DFL:默认
             自定义的信号处理函数
返回值:
    成功:以前信号处理函数
    失败:-1
1)示例代码 

 

 2. sigaction
#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oidact);
参数:
    signum:信号类型,除SIGKILL及SIGSTOP之外的任何一个信号
    act:指向sigaction结构体的指针,包含对特定信号的处理
    oidact:保存信号原先的处理方式
返回值:
    成功:0
    失败:-1

struct sigaction{
    void (*sa_handler)(int signo);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restore)(void);
}

 4.ipcs

ipcs -m :查询显示当前系统的共享内存
ipcs -q  :查询显示当前系统的消息队列
ipcs -s   :查询显示当前系统的信号灯集

ipcrm -m shmid:删除某个共享内存
ipcrm -q msgid:删除某个消息队列
ipcrm -s semid:删除某个信号灯集

4.1 IPC步骤

	  key						    id
ftok ----> shm_get/msg_get/sem_get ----> 
										 shmmat/shmdt/shmctrl
										 msgctrl/msgsend/msgrecv
										 semctrl/semop

4.2 ftok(生成一个key值) 

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id); 
功能:生成一个KEY值
参数:
  pathname: 路径名
  proj_id:  1-255
    
返回值:
  成功: key
  失败: -1

5.共享内存(share memory )

5.1 共享内存的特点 

1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2.为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间

5.2 优缺点 

优点:进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
缺点:由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 

5.3 共享内存的相关操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

步骤:

 	1. ftok
 	2. shmget
 	3. shmat
 	4. 进程间通信 fork
 	5. shmdt
 	6. shmctl
  

int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
参数:
  key: ftok函数的返回值  or  IPC_PRIVATE  
  size: 内存大小  1024bytes 4096bytes
  shmflg:
			IPC_CREAT|0666
返回值:  
  成功:shmid
  失败:-1
        
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射    
参数: 
  shmid: shmget的返回值
  shmaddr: NULL 自动完成映射
  shmflg:
			SHM_RDONLY:只读
      0 :读写  
返回值:
  成功:内存地址
  失败:NULL
 
          
          
int shmdt(const void *shmaddr);          
功能:解除映射
参数:
  shmaddr: shmat函数的返回值
返回值:
   成功:0 
   失败:-1
 
    
int shmctl(int shmid, int cmd, struct shmid_ds
       *buf);    
功能: 控制函数   
参数:    
    shmid: shmget函数的返回值 
    cmd: IPC_RMID 删除共享内存
    buf: NULL  
返回值:
   成功:0
   失败:-1   

5.4 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{ 
    key_t key = ftok("/",1);
    if(key == -1)
    {
        perror("ftok");
        return -1;
    }

    int shmid = shmget(key,1024,IPC_CREAT|0666);
    if(shmid == -1)
    {
        perror("shmget");
        goto xxx;
    }
    printf("shmid:%d\n",shmid);
    system("ipcs -m");//查看当前系统的共享内存
    char *p = shmat(shmid,NULL,0);
    if(p == NULL)
    {
        perror("shmat");
        goto xxx;
    }

    //fork
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        goto xxx;
    }
    else if(pid == 0)
    {
        //子进程 写操作
        char buf[64] = {0};
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);
            strcpy(p,buf);//把buf拷贝到映射中
            if(strncmp(buf,"quit",4) == 0)
            {
                break;
            }
        }
    }
    else
    {
        //父进程 读操作
        waitpid(-1,NULL,WNOHANG);
        while(1)
        {
            printf("p =%s\n",p);
            sleep(1);
            if(strncmp(p,"quit",4) == 0)
            {
                break;
            }
        }
    }
    int ret = shmdt(p);
    if(ret == -1)
    {
        perror("shmdt");
        goto xxx;
    }
    ret = shmctl(shmid,IPC_RMID,NULL);
    if(ret ==-1)
    {
        perror("shmctl");
        goto xxx;
    }
xxx:
    sleep(2);  //防止出错
    char str[100] = {0};
    sprintf(str,"ipcrm -m %d",shmid);
    system(str);
    system("ipcs -m");

    return 0;
} 

5.5 共享内存read/write 

/*read*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{ 
    key_t key = ftok("/",1);
    if(key == -1)
    {
        perror("ftok");
        return -1;
    }

    int shmid = shmget(key,1024,IPC_CREAT|0666);
    if(shmid == -1)
    {
        perror("shmget");
        goto xxx;
    }
    printf("shmid:%d\n",shmid);
    system("ipcs -m");
    char *p = shmat(shmid,NULL,0);
    if(p == NULL)
    {
        perror("shmat");
        goto xxx;
    }

    waitpid(-1,NULL,WNOHANG);
    while(1)
    {
        printf("p =%s\n",p);
        sleep(1);
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    int ret = shmdt(p);
    if(ret == -1)
    {
        perror("shmdt");
        goto xxx;
    }
    ret = shmctl(shmid,IPC_RMID,NULL);
    if(ret ==-1)
    {
        perror("shmctl");
        goto xxx;
    }
xxx:
    sleep(2);
    char str[100] = {0};
    sprintf(str,"ipcrm -m %d",shmid);
    system(str);
    system("ipcs -m");

    return 0;
} 
/*write*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{ 
    key_t key = ftok("/",1);
    if(key == -1)
    {
        perror("ftok");
        return -1;
    }

    int shmid = shmget(key,1024,IPC_CREAT|0666);
    if(shmid == -1)
    {
        perror("shmget");
        goto xxx;
    }
    printf("shmid:%d\n",shmid);
    system("ipcs -m");
    char *p = shmat(shmid,NULL,0);
    if(p == NULL)
    {
        perror("shmat");
        goto xxx;
    }

    char buf[64] = {0};
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        strcpy(p,buf);
        if(strncmp(buf,"quit",4) == 0)
        {
            break;
        }
    }
    int ret = shmdt(p);
    if(ret == -1)
    {
        perror("shmdt");
        goto xxx;
    }
    ret = shmctl(shmid,IPC_RMID,NULL);
    if(ret ==-1)
    {
        perror("shmctl");
        goto xxx;
    }
xxx:
    sleep(2);
    char str[100] = {0};
    sprintf(str,"ipcrm -m %d",shmid);
    system(str);
    system("ipcs -m");

    return 0;
} 

6. 消息队列

 6.1 消息队列的特点

1.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
2.消息队列可以按照类型来发送/接收消息,相同的类型满足先进先出,不同的类型可以随意存取.
3.全双工通信方式

6.2 消息队列的相关操作 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

步骤:
	1. ftok
	2. msgget
	3. 进程间通信
	4. msgsnd
	5. msgrcv
	6. msgctl
  
int msgget(key_t key, int msgflg);
功能:创建消息队列
  
参数:
  key: ftok函数的返回值  or  IPC_PRIVATE
  msgflg: IPC_CREAT|0666
返回值:  
  成功:msgid
  失败:-1

    
		struct msgbuf {
      long mtype;       /* message type, must be > 0 */
      char mtext[N];    /* message data */
    };

struct msgbuf msg;

int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);    
功能:发送数据  
参数:
  msgid: msgget函数的返回值
  msgp:  &msg
  msgsz: 发送正文的长度
    	sizeof(msg) - sizeof(long)
  msgflg:
		IPC_NOWAIT:非阻塞
    0:阻塞  
返回值:
    成功:0
    失败:-1
 
      
struct msgbuf msg;
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收数据       
参数:
  msgid:  msgget函数的返回值
  msgp: &msg
  msgsz:发送正文的长度
        sizeof(msg) - sizeof(long) 
  msgtyp: 消息的类型
  msgflg:
		IPC_NOWAIT:非阻塞
    0:阻塞			 
返回值:        
     成功:接收消息的长度   
     失败:-1   

int msgctl(int msgid, int cmd, struct msqid_ds *buf);        
功能: 删除消息队列      
参数:
  msgid:  msgget函数的返回值
  cmd: IPC_RMID
  buf: NULL
返回值:        
   成功;0 
   失败:-1 

6.3 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <unistd.h>

#define N 100
struct msgbuf{
    long mtype;
    char mtext[N];
};


int main(int argc, char *argv[])
{ 
    key_t key = ftok("/",100);
    if(key == -1)
    {
        perror("ftok");
        return -1;
    }
    int msgid = msgget(key,IPC_CREAT|0666);
    if(msgid == -1)
    {
        perror("msgget");
        goto xxx;
    }

    printf("msgid:%d\n",msgid);
    system("ipcs -q");
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        goto xxx;
    }
    else if( pid == 0)
    {
        struct msgbuf msg;
        char buf[64] = {0};
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);
            msg.mtype = 10;
            strcpy(msg.mtext,buf);
            msgsnd(msgid,&msg,sizeof(msg)-sizeof(long),0);
            if(strncmp(buf,"quit",4)==0)
            {
                break;
            }
        }
    }
    else
    {
        waitpid(-1,NULL,WNOHANG);
        struct msgbuf msg;
        while(1)
        {
            msgrcv(msgid,&msg,sizeof(msg) - sizeof(long),10,0);
            printf("msg:%s\n",msg.mtext);
            if(strncmp(msg.mtext,"quit",4)==0)
            {
                break;
            }
        }
    }
    int ret = msgctl(msgid,IPC_RMID,NULL);
    if(ret ==-1)
    {
        perror("msgctl");
        goto xxx;
    }
xxx:
    sleep(2);
    char str[100] = {0};
    sprintf(str,"ipcrm -q %d",msgid);
    system(str);
    system("ipcs -q");
    



    return 0;
} 

 7.信号量

 信号量一般和共享内存配合使用

7.1 无名信号量 :适用于线程同步

sem_t
sem_init(); 初始化信号量
sem_wait(); P操作    -1
sem_post(); V操作   +1
sem_getvalue() 获得信号的值
1)示例代码 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>

sem_t sem; //信号量

void *fun(void *arg)
{
	int n = 5;
	while(n--)
	{
		printf("xxxx\n");
		sleep(1);
	}

	sem_post(&sem);  //+1

}

int main(int argc, const char *argv[])
{
	//信号量的初始化
	sem_init(&sem, 0, 0);

	pthread_t tid;

	pthread_create(&tid, NULL, fun, NULL);
	pthread_detach(tid);
	
	printf("11111\n");

	sem_wait(&sem);  //-1

	printf("22222\n");

	while(1);

	return 0;
}

 7.2 有名信号量:适用于进程同步

    P操作  sem_wait() -1
    V操作  sem_post() +1
    关闭有名信号量  sem_close()

 1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>

char buf[100] = {0};
sem_t sem; //信号量

void *fun(void *arg)
{

	while(1)
	{
		sem_wait(&sem); //-1		
		printf("%d\n", strlen(buf));
	}

}

int main(int argc, const char *argv[])
{
	//信号量的初始化
	sem_init(&sem, 0, 0);

	pthread_t tid;
	pthread_create(&tid, NULL, fun, NULL);
	pthread_detach(tid);
	

//	sem_wait(&sem);  //-1

	while(1)
	{
		gets(buf);		
		sem_post(&sem); //+1
	}

	return 0;
}

 7.3 信号灯集

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

步骤:
	1. ftok
	2. shmget
	3. shmat

*	4. semget
*	5. semctl
    6. 进程间通信
*	7. semop
*	8. semctl
    9. shmdt
    10. shmctl
  

int semget(key_t key, int nsems, int semflg);
功能:创建信号灯集  
参数:  
  key:ftok的返回值 or IPC_PRIVATE
  nsems: 设置的信号灯的数目
	semflg: 
  	IPC_CREAT|0666
返回值:
     成功:semid
     失败:-1
  
      
union semun {
      int val;  /* Value for SETVAL */
      struct semid_ds *buf;  
      unsigned short  *array;
      struct seminfo  *__buf; 
};
      
int semctl(int semid, int semnum, int cmd, ...);   功能:信号灯集控制函数   
参数:
   semid: semget的返回值
   semnum: 要设置的信号灯的编号  
   cmd:    
      IPC_RMID: 删除信号灯集
      SETVAL: 设置信号灯
   ...: 共用体的变量 or 不写     
返回值:
     成功:0
     失败:-1
 
     
     编号				初值
     0 	 sem0  	0     
     1 	 sem1  	1   

     子进程:写入
       sem1:-1  
       //write       
       sem0:+1  
		 父进程:读取 
       sem0:-1
       //read  
       sem1:+1  
       
int semop(int semid, struct sembuf *sops, unsigned nsops);     
功能:对信号灯进行PV操作  -1  +1     
参数:
  semid: semget的返回值
	sops: 结构体的变量的地址
    struct sembuf{
    	unsigned short sem_num;  /* semaphore number 信号灯的编号  */
			short sem_op;   /* semaphore operation 信号灯操作-1 +1*/
			short sem_flg;  /* operation flags 0:阻塞 */
    }
	nsops: 信号灯的个数 默认就是1
返回值:
    成功:0
    失败:-1
 1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>

union semun {
    int val;  /* Value for SETVAL */
    struct semid_ds *buf;  
    unsigned short  *array;
    struct seminfo  *__buf; 
};

/*
   struct sembuf{
   unsigned short sem_num;
   short sem_op;
   short sem_flg;
   };
   */

int main(int argc, const char *argv[])
{
    key_t key = ftok("/", 1);
    if(key == -1)
    {
        perror("ftok");
        return -1;
    }

    int shmid = shmget(key, 1024, IPC_CREAT|0666);
    if(shmid == -1)
    {
        perror("shmget");
        goto xxx;
    }

    printf("shmid:%d\n",shmid);

    system("ipcs -m"); 

    char *p = shmat(shmid, NULL, 0);
    if(p == NULL)
    {
        perror("shmat");
        goto xxx;
    }


    //加入信号灯集来控制共享内存
    int semid = semget(key, 2, IPC_CREAT|0666);
    if(semid == -1)
    {
        perror("semget");
        goto xxx;
    }

    //设置初值
    union semun sem0 = {0};
    union semun sem1 = {1};

    //设置编号
    semctl(semid, 0, SETVAL, sem0);
    semctl(semid, 1, SETVAL, sem1);

    //fork
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        goto xxx;
    }
    else if(pid == 0)
    {
        //子进程 写操作

        char buf[64] = {0};

        struct sembuf sem0;
        sem0.sem_num = 1; //编号
        sem0.sem_op = -1; //PV操作 +1 -1
        sem0.sem_flg = 0;

        struct sembuf sem1;
        sem1.sem_num = 0;
        sem1.sem_op = 1;
        sem1.sem_flg = 0;

        while(1)
        {
            semop(semid, &sem0, 1);

            fgets(buf,sizeof(buf),stdin);

            strcpy(p, buf);

            if(strncmp(buf, "quit", 4) == 0)
            {
                break;
            }

            semop(semid, &sem1, 1);
        }

    }
    else{
        //父进程 读操作
        waitpid(-1, NULL, WNOHANG);

        struct sembuf sem0;
        sem0.sem_num = 0;
        sem0.sem_op = -1;
        sem0.sem_flg = 0;

        struct sembuf sem1;
        sem1.sem_num = 1;
        sem1.sem_op = 1;
        sem1.sem_flg = 0;

        while(1)
        {
            semop(semid, &sem0, 1);

            printf("p=%s\n", p);
            sleep(1);

            if(strncmp(p, "quit", 4) == 0)
            {
                break;
            }

            semop(semid, &sem1, 1);
        }

    }

    int ret = semctl(semid, 0|1, IPC_RMID);
    if(ret == -1)
    {
        perror("semctl");
        goto xxx;
    }

    ret = shmdt(p);
    if(ret == -1)
    {
        perror("shmdt");
        goto xxx;
    }

    ret = shmctl(shmid, IPC_RMID, NULL);
    if(ret == -1)
    {
        perror("shmctl");
        goto xxx;
    }

xxx:	
    sleep(2); //防止出错

    char str[100] = {0};

    sprintf(str, "ipcrm -m %d", shmid);

    system(str);

    system("ipcs -m");

    return 0;
}

8.套接字(socket)

 见网络编程知识总结篇(即下一篇嵌入式系列学习)

八、往期回顾 

1. C语言基础笔记(嵌入式系列学习1)-CSDN博客

2. C语言基础练习题总结(嵌入式系列学习2)-CSDN博客

3. Linux基础、C语言高级基础知识总结(嵌入式系列学习3)-CSDN博客 

4. 数据结构与算法知识总结(嵌入式系列学习4)-CSDN博客 

持续更新中.......

更多文章,点击左上角头像,查看个人简介和更多相关项目的分享,也可通过关注我的其他平台,一起学习,一起进步!Come on!!!动起手来,开始探索~~~   

  • 35
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式-箫声.

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值