IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> IO进线程编程总结! -> 正文阅读

[系统运维]IO进线程编程总结!

一、标准IO

1.标准IO------(C库提供)
C库函数:在系统调用接口之上封装的接口,一个C库函数可以封装多个系统调用函数。
作用:

  1. 增强了代码的可移植性,复用性
  2. 提高了效率。
    标准IO增加了一个【缓冲机制】

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

特点:

  1. 不带缓冲区
  2. 操作系统直接提供的函数接口
  3. 调用系统调用是很耗费资源的

1. 标椎IO (c库)
FILE fopen(const char path, const char mode);
/
***************************************************************

  • 功能: 打开文件
  • 参数: path:带路径的文件名,mode:打开方式 r:只读 w:只写
  • 返回值: 成功返回FILE指针。失败返回NULL
    *******************************************************************/

r: 以读的方式打开一个文件,同时文件指针指向文件开头。
r+: 以读写的方式打开一个文件,同时文件指针指向文件开头。

w:以写的方式打开一个文件,如果文件存在清空,如果不存在创建文件,同时文件指针指向文件开头。
w+: 以读写的方式打开一个文件,如果文件存在清空,如果不存在创建文件,
同时文件指针指向文件开头。

a:以写的方式打开一个文件,如果不存在创建文件,如果文件存在以追加方式写,同时文件指针指向文件结尾。

a+:以读写的方式打开一个文件,如果不存在创建文件,如果文件存在读文件指针在文件开头,写文件指针在文件结尾(追加方式写)

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

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

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

vi -t FILE

vi -t _IO_FILE

struct _IO_FILE {

char* _IO_buf_base; /* Start of reserve area. /
char
_IO_buf_end; /* End of reserve area. */

int _fileno;
}

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

void perror(const char s);
/
***************************************************************

  • 功能:根据 errno 打印出错误信息
  • 参数:@s 提示用的字符串 可以写任意字符串
  • 返回值: void
    *****************************************************************/

int fclose(FILE fp);
/
*****************************************************************

  • 功能: 关闭文件
  • 参数: 文件指针
  • 返回值: 成功返回0 失败返回-1
    *******************************************************************/
/*创建一个文件*/
#include <stdio.h>
int main(void)
{
    FILE *fp = fopen("./1.txt", "w");
    if(NULL == fp)
    {
        perror("fopen\n");  
        return -1;  
    }
    
    printf("fopen success!\n");
    
    fclose(fp);	//关闭流指针
    
    return 0;
} 

为什么要关闭一个文件?

一、防止其他进程操作这个文件
二、释放结构体占用的资源
在程序结束时,系统自动回收资源(不完全),所以尽量写上fclose。

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

#include <stdio.h>


int main(void)
{
    FILE *fp = fopen("./1.txt", "r");
    if(NULL == fp)
    {
        perror("fopen");  
        return -1;  
    }
        

    int n = fgetc(fp);

    printf("n = %d\n", n);
    
    printf("len = %d\n", fp->_IO_buf_end - fp->_IO_buf_base);
   
    printf("fopen success!\n");
    
    fclose(fp);	//关闭流指针
    
     fputc(fgetc(stdin),stdout);
     fputc('\n', stdout);
    
    return 0;
} 

int fgetc(FILE *stream);
/******************************************************************

  • 功能: 读文件 (按字符读取)
  • 参数: stream:文件
  • 返回值: 成功返字符编码值 失败返回-1
    *******************************************************************/

int fputc(int c, FILE *stream);
/******************************************************************

  • 功能: 写文件 (按字符写)
  • 参数: stream:文件
  • 返回值: 成功返字符编码值 失败返回-1
    *******************************************************************/
/*追加aaaaaaaaaa*/
#include <stdio.h>


int main(void)
{
    FILE *fp = fopen("./1.txt", "r+");
    if(NULL == fp)
    {
        perror("fopen");  
        return -1;  
    }


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

    int i = 0;
    for(i=0; i<10; i++)
    {
	    fputc('a', fp);	//:aaaaaaaaaa	//写入
    }

    printf("fopen success!\n");
    
    fclose(fp);		//关闭流指针
    
    return 0;
} 
 
 
/*
运行结果:
aaaaaaaaaa
fopen success!
*/

每一个终端都是一个文件: pts/xxx 这个就是终端对应的文件,这个文件的名字是以数字命名的
这个文件存储在 : /dev/pts/xxx
这些文件是由linux系统自动创建。当打开一个终端时,就会重建一个新的文件与之对应
stdin、stdout、stderr都指向的是同一个文件(终端文件)。

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

/*向一个文件写入内容*/
#include <stdio.h>

int main(void)
{
	FILE *fp = fopen("./2", "w");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}

	fputc('x', fp);
	fputc('\n', fp);

	fputc('b', fp);
	fputc('\n', fp);

	fclose(fp);	//关闭流指针

	return 0;
} 
/*
运行结果:新文件2内容,cat 2 查看
x
d
*/

缓冲区: 行缓存、无缓存、全缓存

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

//1.一行满了 或遇到’\n’输出条件
//2.fflush可以强制刷新
//3.文件关闭的时候 fclose(stdout) exit return

int fflush(FILE *stream);
/*******************************

  • 功能: 刷新缓存区
  • 参数:@stream 流指针
  • 返回值: 成功返回 0,
  • 错误返回 EOF 更新 errno
    *******************************/
/*行缓存:只有当遇见'\n'时,才刷新缓存区*/
#include <stdio.h>

int main(void)
{
    printf("hello word");	// '\n'
    printf("line size = %d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);	//:1024 ;行缓存大小都是1024
    
    int i = 0;
    for(i=0; i<1024; i++)		//前面占有字符,这里添加998即可占满
    {
    	fputc('a', stdout);      //添加'a'满的情况     
    }
    
    //fclose(stdout);
    //exit(0);
    //return 1;
    //fflush (stdout);
    
    while(1);         		//死循环      

	return 0;
} 

/*
运行结果:行缓存占满

*/

无缓存:stderr

/*无缓存*/
#include <stdio.h>

int main(void)
{
	fputc('a', stdout);
	fputc('b', stdout);
	//fputc('\n', stdout);	//不加'\n',a,b不打印


	fputc('a', stderr);	//:a
	fputc('b',stderr);	//:b

	while(1);

	return 0;
} 

全缓存:通过fopen函数打开的流指针,这个流指针fp的缓冲区大小是 4*1024 4Kbyte
//1.缓存区满
//2.fclose(fp)
//3.return
//4.exit
//5.fflush(fp)

/*全缓存:占4096,4k*/
#include <stdio.h>

int main(void)
{
	FILE *fp = fopen("./1.txt", "w");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}

	int i = 0;
	for(i=0; i<4097; i++)	//当缓存区4096之后满了将数据存储到缓存区去
				//1.txt文件内容才打印
	{
		fputc('a', fp);         
	}

	while(1);          		    

	return 0;
} 
/*
运行结果:当全缓存没满时,数据不会打印到缓存区,占满才会放入1.txt中

*/

char *fgets(char *buf, int size, FILE *stream);
/******************************************************************

  • 功能: 读文件(按行读取)
  • 参数: buf:地址
    size :读取的字节数
    stream: 流指针
  • 返回值: 成功返回读取到的字符串的首地址 失败返回NULL
    *******************************************************************/
    文件有多少行?
/*fgets用法,判断文件有多少行?*/
#include <stdio.h>
#include <string.h>

int main(void)
{
	FILE *fp = fopen("./1.txt", "r");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}

	char buf[100];
	int cont = 0;
	while(1)
	{
		char *p = fgets(buf, sizeof(buf), fp);
		if(NULL == p)
		{
			perror("fgets");
			break;
		}
		printf("p = %s", p);

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

		cont++;
	}

	printf("count = %d\n", cont);
 
  	fclose(fp);	//关闭流指针

	return 0;
} 
/*
运行结果:计算文件有多少行。

*/

int fputs(const char *buf, FILE *stream);
/******************************************************************

  • 功能: 写文件(按行写)
  • 参数:
  • 返回值: 成功返回0 失败返回-1
    *******************************************************************/
    写入"hello world"

示例:从标椎输入stdin 字符串到文件当中

练习:复制文件

/*fputs用法终端输入数据到文件中,可以输入quit直接退出*/
#include <stdio.h>
#include <string.h>

int main(void)
{
	FILE *fp = fopen("./1.txt", "w");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}

	char buf[50];

	while(fgets(buf, sizeof(buf), stdin) != NULL)
	{
		if(strcmp(buf,"quit\n")==0)	//输入quit直接退出
		{
			break;
		}

		fputs(buf, fp);
	}

	printf("success!\n");
	
	fclose(fp);

	return 0;
} 

练习:复制文件

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

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

	char buf[100];
	while( fgets(buf, sizeof(buf), fp1) != NULL )
	{
		fputs(buf, fp2);
	}
	
	printf("copy success!\n");

	fclose(fp1);
	fclose(fp2);


	return 0;
}

int fseek(FILE *stream, long offset, int whence);
/******************************************************************

  • 功能: 文件定位

  • 参数: stream:文件,
    offset:偏移量
    偏移的字节数 +100 -100
    whence:基准值
    SEEK_SET: 文件开头
    SEEK_CUR: 文件当前
    SEEK_END: 文件末尾

  • 返回值: 失败返回-1 成功返回:相对起点的偏移量。
    *******************************************************************/
    定位到文件末尾的前一个字节 fseek(fp, -1 , SEEK_END);

long ftell(FILE *stream);
/*********************************************
*功能: 获取文件指针偏移位置
*参数: @stream 文件流
*返回值: 成功: 返回 相对于文件开头的偏移值

  • 失败: 返回 -1 更新 errno
    ***********************************************/
    获取文件大小

void rewind(FILE *stream);
/*********************************************
*功能: 移动文件指针,移动到文件开头

  • 等价于
  • (void) fseek(stream, 0L, SEEK_SET)
    *参数: @stream 文件流
    *返回值: void
    ***********************************************/
#include <stdio.h>
#include <string.h>

int main(void)
{
	FILE *fp = fopen("./1.txt", "r+");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}
	
	fseek(fp, 0, SEEK_END);	//移动末尾

	fputc('x', fp);

	int num = ftell(fp);	//统计字节数
	printf("num = %d\n", num);

	rewind(fp);		//移动到开头
	
	int num2 = ftell(fp);
	printf("num = %d\n", num2);


	printf("success!\n");
	
	fclose(fp);

	return 0;
}
/*
运行结果:
num = 22
num = 0
success!
*/

练习:查单词

伪代码{
1. 打开文件
2. 循环
3. 输入一个单词
4. 遍历文件
5. 打印出单词信息
6. 重新开始查询
7. 关闭文件
}

//查单词
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <dirent.h>

int Stat(char *filename)
{
	struct stat my_stat;

	int ret = stat(filename, &my_stat);
	if(ret == -1)
	{
		perror("stat");
		return -1;
	}

	if(S_ISREG(my_stat.st_mode))
		printf("-");
	else if(S_ISDIR(my_stat.st_mode))
		printf("d");
	else if(S_ISCHR(my_stat.st_mode))
		printf("c");
	else if(S_ISBLK(my_stat.st_mode))
		printf("b");
	else if(S_ISFIFO(my_stat.st_mode))
		printf("p");
	else if(S_ISSOCK(my_stat.st_mode))
		printf("s");
	else
		printf("l");

	
	printf("%c", (my_stat.st_mode & (1 << 8))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 7))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 6))?'x':'-' );

	printf("%c", (my_stat.st_mode & (1 << 5))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 4))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 3))?'x':'-' );

	printf("%c", (my_stat.st_mode & (1 << 2))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 1))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 0))?'x':'-' );

    printf(" %ld", my_stat.st_nlink);

    struct passwd *p = getpwuid(my_stat.st_uid);
    printf(" %s", p->pw_name);

    struct group *q = getgrgid(my_stat.st_gid);                              
    printf(" %s", q->gr_name); 

	printf("%6ld ", my_stat.st_size);

	time_t t;
	time(&t);

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

	return 0;
}

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

	DIR *dir = opendir(argv[1]);
	if(NULL == dir)
	{
		perror("opendir");
		return -1;
	}

	while(1)
	{
		struct dirent *p = readdir(dir);
		if(p == NULL)
			break;
		if( strncmp(p->d_name, ".", 1) == 0)
			continue; 
		Stat(p->d_name);
		printf("%s\n", p->d_name);
	}

	closedir(dir);

	return 0;
}

在这里插入图片描述
【Printf函数升级】
int fprintf(FILE *stream, const char *format, …);
/***********************************************
*功能: 将格式化的字符串,输出到指定的流指针中(stream)
*参数:

  • @stream 流指针
  • @format 格式化字符串
  • @… 不定参数
    *返回值:
  • 成功: 格式化的字符串的长度
  • 失败: 负数 更新errno
    ***********************************************/

int sprintf(char *str, const char *format, …);
/**********************************
*功能:格式化输出字符串到str指向的内存空间中
*参数:@str 字符串内存空间首地址 注意内存空间

  • @format 格式化输出内容
  • @… 不定参数
    *返回值: 成功 格式化的的字符数
  • 失败 负数
    ***********************************/

int sscanf(const char *str, const char *format, …);
/**********************************
*功能:以固定字符串为输入源进行输入
*参数:@str 字符串内存空间首地址 注意内存空间

  • @format
  • @… 不定参数
    *返回值: 成功则返回参数数目
  • 失败则返回-1
    ***********************************/
#include <stdio.h>
#include <string.h>

int main(void)
{
	FILE *fp = fopen("./dict.txt", "rb");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}
	char buf[100];
 
   int ret = fread(buf, 50, 2, fp);
   
   printf("ret = %d %s", ret, buf);           



	printf("success!\n");
	
	fclose(fp);

	return 0;
}
/*
运行结果:

*/

当我们用 UE 打开一个二进制文件(图片,视频)的时候,发现文件中到处都是文本的标志性字符,但是对于 fread 和 fwrite 来说,一视同仁,都是一个普通的字节而己,
所以,二进制文件的读写就要用对文本标记不敏感的 fread 和 fwrite 来进行。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
/******************************************************************

  • 功能: 读文件 (按块读取)
  • 参数: stream:文件,ptr:保存内容地址,size:一块大小,nmemb:块数
  • 返回值: 成功返回块数,失败或到达文件末尾返回值小于块数。
    *******************************************************************/
    例如:从文件fp里读取100个字节,可用以下语句

fread(buffer,100,1,fp);
fread(buffer,50,2,fp);
fread(buffer,1,100,fp);

/*fread用法*/
#include <stdio.h>
#include <string.h>

int main(void)
{
	FILE *fp = fopen("./dict.txt", "rb");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}
	char buf[100];
 
   int ret = fread(buf, 50, 2, fp);
   
   printf("ret = %d %s", ret, buf);           



	printf("success!\n");
	
	fclose(fp);

	return 0;
}
/*
运行结果:

*/

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
/******************************************************************

  • 功能: 写文件 (按块写)
  • 参数: stream:文件,ptr:保存内容地址,size:一块大小,nmemb:块数
  • 返回值: 成功返回块数,失败或到达文件末尾返回值小于块数。
    *******************************************************************/
/*fwrite用法*/
#include <stdio.h>
#include <string.h>

 typedef struct node{
     int a;
     char b;
     float c; 
 }node_t;

int main(void)
{
	FILE *fp = fopen("./a.txt", "wb");
	if(NULL == fp)
	{
		perror("fopen");  
		return -1;  
	}
	
 	node_t XX = {15, 'a', 3.14};
  
  	fwrite(&XX, sizeof(node_t), 1, fp);
   
   printf("fwrite success!\n");
   
	
	fclose(fp);

	return 0;
}
/*
运行结果:

*/

练习:怪兽家园

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

/*怪兽家园*/
#include <stdio.h>
#include <string.h>

#define N (50)

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

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

	printf("set monster number!\n");

	int i = 0;
	for(i=0; i<n; i++)
	{
		printf("第%d个怪兽!\n", i+1);
		printf("ID 名字 种族 战斗类型\n");
		scanf("%d %s %s %s", &m1[i].ID, m1[i].name, m1[i].species, m1[i].type);
	}

	printf("怪兽信息输入完毕!请保存!\n");
	
	FILE *fp = fopen("./monster.txt", "wb+");
	if(NULL == fp)
	{
		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);
	}

}

int main(void)
{

	createMonster(3);


	return 0;
}

【Printf函数升级】
int fprintf(FILE *stream, const char *format, …);
/***********************************************
*功能: 将格式化的字符串,输出到指定的流指针中(stream)
*参数:

  • @stream 流指针【stdout / FILE *fp】
  • @format 格式化字符串
  • @… 不定参数
    *返回值:
  • 成功: 格式化的字符串的长度
  • 失败: 负数 更新errno
    ***********************************************/
/*fprintf用法*/
#include <stdio.h>
#include <string.h>

int main(void)
{

	printf("hello world\n");
	int a = 100;
	printf("a = %d\n", a);

	fprintf(stdout, "%d-%d-%d\n", 2121,07,02);

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

	fprintf(fp, "%d-%d-%d-%d-%d\n", 2121, 07, 02, 11, 56);

	return 0;
}

int sprintf(char *str, const char *format, …);
/**********************************
*功能:将格式化的字符串,输出到内存空间中
*参数:@str 字符串内存空间首地址 注意内存空间

  • @format 格式化输出内容
  • @… 不定参数
    *返回值: 成功 格式化的的字符数
  • 失败 负数
    ***********************************/
/*sprintf用法*/
#include <stdio.h>
#include <string.h>

int main(void)
{

	char buf[100];


	sprintf(buf, "%d-%d-%d\n", 2121,07,02);

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

	return 0;
}

int sscanf(const char *str, const char *format, …);
/**********************************
*功能:利用它可以从字符串中取出整数、浮点数和字符串等等
*参数:@str 字符串内存空间首地址 注意内存空间

  • @format
  • @… 不定参数
    *返回值: 成功则返回参数数目
  • 失败则返回-1
    ***********************************/
/*sscanf的用法*/
#include <stdio.h>
#include <string.h>

int main(void)
{
	int a, b, c;

	sscanf("2121-7-2", "%d-%d-%d", &a, &b, &c);
	printf("%d %d %d\n", a, b, c);
	
	float a1, b1, c1;

	sscanf("3.14-1.5-9.27", "%f-%f-%f", &a1, &b1, &c1);
	printf("%f %f %f\n", a1, b1, c1);

	return 0;
}

FILE *freopen(const char *path, const char *mode, FILE *stream);
/**********************************
*功能:改变某个流的输出方向 作用: 日志文件
*参数:@path 路径
*@mode 打开方式

  • @stream 流指针
    *返回值: 成功则返回
  • 失败则返回NULL
    ***********************************/

#incude <time.h>
time_t time(time_t *t);
/*********************************************
*功能: 获取从1970-1-1 00:00:00到现在的秒数
*参数: @t time_t 指针变量,自己定义一个time_t

  • 类型的变量,然后将变量的地址作为参数传入time()
    *返回值: 成功: 返回时间值,秒数
  • 失败: 返回 -1 更新 errno
    ***********************************************/

#include <time.h>
struct tm *localtime(const time_t *timep);
/*********************************************
*功能: 将从1970-1-1 00:00:00到现在的秒数,转换为日历时间
*参数: @tloc time_t 指针变量,自己定义一个time_t

  • 类型的变量,然后将变量的地址作为参数传入time()
    *返回值: 成功: 返回日历结构体
  • 失败: 返回 NULL 更新 errno
    ***********************************************/

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 /
int tm_wday; /
Day of the week (0-6, Sunday = 0) /
int tm_yday; /
Day in the year (0-365, 1 Jan = 0) /
int tm_isdst; /
Daylight saving time */
};

示例:获取系统时间

练习:获取系统时间,并将系统时间 打印输出到 文件中
[2020-05-07 14:07:30]
[2020-05-07 14:07:31]
[2020-05-07 14:07:32]

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

int main(void)
{
	while(1)
	{
		time_t t;

		time(&t);

		//printf("tim:%d\n",m);

		struct tm *p = localtime(&t);
		printf("%d %d %d\n", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday);

		sleep(1);
	}

	return 0;
}
/*更新日志:*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main(int argc, const char *argv[])
{
	while(1)
	{
		FILE *fp = fopen("time.txt", "a");
		if(NULL == fp)
		{
			perror("fopen");
			return -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);

	}
	return 0;
}

二、文件IO

1、文件IO

文件描述符

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

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

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

//打印文件描述符

fdopen: 将文件描述符转化为对应的流指针
FILE *fdopen(int fd, const char *mode);

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
/*****************************************
*功能: 打开文件,获取文件描述符
*参数:
*@pathname 文件名(指定完整路径)
*@flags 打开文件的方式
*O_RDONLY 只读方式打开
*O_WRONLY 只写方式打开
*O_RDWR 读写方式打开
*O_APPEND 如果文件存在,则以追加方式打开
*O_CREAT 如果文件不存在,则创建
*O_TRUNC 如果文件存在,则清空 ( 主要讲解 或 运算 )
*@mode 当指定O_CREAT,则表示创建文件的文件权限(user group other)

  • 如果未指定O_CREAT,则被忽略
    *返回值: 成功 返回文件描述符
  • 失败 返回 -1 更新 errno
    ******************************************/
/*open/close*/
#include <stdio.h>
#include <string.h>
#include <time.h>

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

#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fp = open("a.txt", O_RDONLY | O_CREAT, 0666);
	if(fp < 0)
	{
		perror("open");
		return -1;
	}

	printf("open succes!\n");

	close(fp);

	return 0;
}

#include <unistd.h>
int close(int fd);
/*****************************************
*功能: 关闭文件
*参数: @fd 文件描述符
*
*返回值: 成功 返回 0

  • 失败 返回 -1 更新 errno
    ******************************************/

标准IO 文件IO
r O_RDONLY 只读文件; 且文件必须存在
r+ O_RDWR 可读写文件; 且且文件必须存在
w O_WRONLY | O_CREAT | O_TRUNC 只写文件; 若文件存在则长度清0,擦除文件之前内容; 文件不存在则创建
w+ O_RDWR | O_CREAT | O_TRUNC 可读写文件; 若文件存在则长度清0,擦除文件之前内容; 文件不存在则创建
a O_WRONLY | O_CREAT | O_APPEND 追加方式打开只写文件; 文件不存在则创建; 如果文件存在,写入数据追加到文件尾,保留原先内容
a+ O_RDWR | O_CREAT | O_APPEND 追加方式打开可读写写文件; 文件不存在则创建; 如果文件存在,从头读取,写入数据追加到文件尾,保留原先内容

实际创建的文件权限需要经过一个公式计算得到: mode & (~umask)

0777 111 111 111
0002 000 000 010

111 111 111
&
111 111 101

–>

111 111 101 (0775)

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
/*****************************************
*功能: 读文件
*参数: @fd 文件描述符

  • @buf 从文件中读取的内容放到内存的那个位置
    *@count 要读取多少字节

*返回值: 成功 返回 读取的字节个数

  • 返回值为 0 表示读到文件结尾
  • 失败 返回 -1 更新 errno
    ******************************************/

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
/*****************************************
*功能: 写文件
*参数: @fd 文件描述符

  • @buf 要把内存中那些内容写到文件中
  • @count 要写入多少字节

*返回值: 成功 返回 写入的字节个数

  • 失败 返回 -1 更新 errno
    ******************************************/
/*write/read*/
#include <stdio.h>
#include <string.h>
#include <time.h>

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

#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fp = open("a.txt", O_RDWR | O_CREAT, 0666);	//读写方式打开
	if(fp < 0)
	{
		perror("open");
		return -1;
	}

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

	printf("open succes!\n");

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

	int ret = write(fp, buf, sizeof(buf));
	
	printf("ret = %d\n", ret);


	lseek(fp, 0, SEEK_SET);		//指针偏移到头

	int num = read(fp, buf, ret);

	printf("num = %d, %s\n", num, buf);

	close(fp);

	return 0;
}
/*复制文件*/
#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 fd1 = open("dict.txt", O_RDONLY);
	if(fd1 < 0)
	{
		perror("open");
		return -1;
	}
	int fd2 = open("3.txt", O_WRONLY | O_CREAT, 0666);
	if(fd2 < 0)
	{
		perror("open");
		return -1;
	}

	char buf[100];

	int n;
	
	while(	(n = read(fd1, buf, 100)) > 0 )
	{
		write(fd2, buf, n);
	}

	printf("copy success!\n");

	close(fd1);
	close(fd2);

	return 0;
}
/*查询文件路径下的文件内容*/
#include <stdio.h>
#include <string.h>
#include <time.h>

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

#include <unistd.h>

#include <dirent.h>

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

	DIR *dir = opendir(argv[1]);
	if(NULL == dir)
	{
		perror("opendir");
		return -1;
	}

	while(1)
	{
		struct dirent *p = readdir(dir);
		if(p == NULL)
		{
			break;
		}
    		
      	//不显示.
 		if(strncmp(p->d_name, ".", 1) == 0)
   		continue;	//放弃本次循环,执行下一个循环
     
		printf("%s\n", p->d_name);
	}

	closedir(dir);

	return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

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

	struct stat my_stat;

	int ret = stat(argv[1], &my_stat);
	if(ret == -1)
	{
		perror("stat");
		return -1;
	}

	printf("size = %ld\n", my_stat.st_size);


	struct passwd *p = getpwuid(my_stat.st_uid);

	printf("%s\n", p->pw_name);


	struct group *q = getgrgid(my_stat.st_gid);

	printf("%s\n", q->gr_name);


	time_t t;
	time(&t);

	struct tm *k = localtime(&my_stat.st_mtime);
	printf("%d-%d-%d %d:%d:%d\n", k->tm_year+1900, k->tm_mon+1, k->tm_mday,
k->tm_hour,k->tm_min,k->tm_sec
);

	if(S_ISREG(my_stat.st_mode))
		printf("-\n");
	else if(S_ISDIR(my_stat.st_mode))
		printf("d\n");
	else if(S_ISCHR(my_stat.st_mode))
		printf("c\n");
	else if(S_ISBLK(my_stat.st_mode))
		printf("b\n");
	else if(S_ISFIFO(my_stat.st_mode))
		printf("p\n");
	else if(S_ISSOCK(my_stat.st_mode))
		printf("s\n");
	else
		printf("l\n");

	
	printf("user %c", (my_stat.st_mode & (1 << 8))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 7))?'w':'-' );
	printf("%c\n", (my_stat.st_mode & (1 << 6))?'x':'-' );

	printf("group %c", (my_stat.st_mode & (1 << 5))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 4))?'w':'-' );
	printf("%c\n", (my_stat.st_mode & (1 << 3))?'x':'-' );

	printf("other %c", (my_stat.st_mode & (1 << 2))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 1))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 0))?'x':'-' );

	puts("");

	return 0;
}

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*****************************************
*功能: 移动文件指针
*参数: @fd 文件描述符

  • @offset 偏移值
    *@whence
    *SEEK_SET: 文件开头位置
    *SEEK_CUR: 当前文件指针位置
    *SEEK_END: 文件结尾位置

*返回值: 成功 返回 设置后的文件指针相对于文件开始的偏移值

  • 失败 返回 -1 更新 errno
    ******************************************/

【目录操作函数】

include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
/*****************************************
*功能: 打开一个目录文件
*参数: @name 目录文件路径
*返回值: 成功 返回 目录流指针
*失败 返回 NULL 更新 errno
******************************************/

#include <dirent.h>
struct dirent *readdir(DIR *dirp);
/*****************************************
*功能: 读目录流指针
*参数: @dirp 目录流指针
*返回值: 成功 返回 struct dirent 指针

  • 失败 返回 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 */
    };

#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
/*****************************************
*功能: 关闭目录流
*参数: @dirp 目录流指针
*返回值: 成功 返回 0
*失败 返回 -1 更新 errno
******************************************/

示例:查看目录下的文件

【文件信息函数】

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
/*****************************************
*功能: 获取指定文件的信息
*参数: @path 指定文件路径

  • @buf 文件信息保存在指定的内存地址中

*返回值: 成功 返回 0

  • 失败 返回 -1 更新 errno
    *****************************************/
    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 */
    };

将uid 转换为 用户名
#include <sys/types.h>
#include <pwd.h>
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 */
};

将gid 转换为 组名
#include <sys/types.h>
#include <grp.h>
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 */
};

示例:stat.c

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

练习: 完成ls -l

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <dirent.h>

int Stat(char *filename)
{
	struct stat my_stat;

	int ret = stat(filename, &my_stat);
	if(ret == -1)
	{
		perror("stat");
		return -1;
	}

	if(S_ISREG(my_stat.st_mode))
		printf("-");
	else if(S_ISDIR(my_stat.st_mode))
		printf("d");
	else if(S_ISCHR(my_stat.st_mode))
		printf("c");
	else if(S_ISBLK(my_stat.st_mode))
		printf("b");
	else if(S_ISFIFO(my_stat.st_mode))
		printf("p");
	else if(S_ISSOCK(my_stat.st_mode))
		printf("s");
	else
		printf("l");

	
	printf("%c", (my_stat.st_mode & (1 << 8))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 7))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 6))?'x':'-' );

	printf("%c", (my_stat.st_mode & (1 << 5))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 4))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 3))?'x':'-' );

	printf("%c", (my_stat.st_mode & (1 << 2))?'r':'-' );
	printf("%c", (my_stat.st_mode & (1 << 1))?'w':'-' );
	printf("%c", (my_stat.st_mode & (1 << 0))?'x':'-' );

    printf(" %ld", my_stat.st_nlink);

    struct passwd *p = getpwuid(my_stat.st_uid);
    printf(" %s", p->pw_name);

    struct group *q = getgrgid(my_stat.st_gid);                              
    printf(" %s", q->gr_name); 

	printf("%6ld ", my_stat.st_size);

	time_t t;
	time(&t);

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

	return 0;
}

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

	DIR *dir = opendir(argv[1]);
	if(NULL == dir)
	{
		perror("opendir");
		return -1;
	}

	while(1)
	{
		struct dirent *p = readdir(dir);
		if(p == NULL)
			break;
		if( strncmp(p->d_name, ".", 1) == 0)
			continue; 
		Stat(p->d_name);
		printf("%s\n", p->d_name);
	}

	closedir(dir);

	return 0;
}

在这里插入图片描述
【库的制作】

1、什么是库
/lib/i386-linux-gnu
/usr/include

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

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

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

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

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

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

    window *.lib *.dll

    $ gcc -E a.c -o a.i // 预编译 (预处理)
    $ gcc -s a.i -o a.s // 编译 C语法装换为汇编语法
    $ gcc -c a.s -o a.o // 汇编 汇编语法转换为二进制机器码
    $ gcc a.o -o a.out // 可执行程序

2、静态库的制作和使用
1. 制作
$ gcc -c hello.c -o hello.o
$ ar -crs libxxx.a hello.o

静态库的命名规范:
必须以lib开头,紧跟库的名字,跟扩展名 .a
例如: libxxx.a

  1. 使用
    $ gcc main.c -L路径 -lxxx
    -L: 指定静态库所在的目录
    -l: 指定静态库的名字 xxx部分

  2. 运行
    $ ./a.out

    优点:a.out 运行后不需要库,可以直接运行
    缺点:
    每个a.out都要包含库,体积较大, 浪费资源;
    对程序更新,部署,发布带来麻烦;

3、动态库的制作和使用
1. 制作
$ gcc -fPIC -c add.c -o add.o
$ gcc -shared -o libxxx.so add.o
动态库的命名规范:
必须以lib开头,紧跟库的名字,跟扩展名 .so
例如: libxxx.so

  1. 使用
    $ gcc main.c -L路径 -lxxx

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

  2. 运行
    $ ./a.out # 会报错

    动态库的搜索方式(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 vim my.conf
      添加路径
      sudo ldconfig生效

    特点:在编译时不会链接到可执行文件中,只是再其中保存一个索引,在运行时,才真正的链接(动态),因此可执行程序体积小。
    优点:
    a.out 体积较小, 节约资源;
    只需要修改.so动态库,有利于程序的更新,部署,发布;
    缺点:a.out 运行后需要库,不能直接运行。

三、进程

3.1.1、理论知识:
(1)、进程:程序的一次执行过程,是系统资源分配和调度的最小单位,是动态的。
(2)、程序:存储在磁盘上的指令的有序集合,是静态的。

3.1.2、进程的内容:
数据段:全局变量,malloc分配的空间
正文段:程序的代码行
堆栈段:函数返回值、参数、局部变量等

3.1.3、进程的内存管理
正文段、用户数据段、系统数据段。

3.1.4、进程号(PID)
唯一的标识一个进程

3.1.5、进程的类型:
(1)交互进程:由 shell 控制和运行的进程,也就是在终端中运行产生的进程。
ctrl+z: 使进程进入挂起状态,被挂起的进程被称为作业
jobs -l: 查看挂起的进程
bg % 作业号: 使这个作业恢复前台运行,不能Ctrl+c结束,将挂起的进程在后台运行。
fg % 作业号: 使这个作业恢复前台运行,可以Ctrl+c结束,把后台运行的进程发在前台运行。
&: 与bg一样
kill -l: 查看信号种类
kill -9 PID: 杀死进程
ps -ajx: 查看进程的运行状态

(2)批处理进程:不属于某个终端,是在队列中被顺序执行,操作系统开机时就有很多批处理进程。
一般做运维的。

(3)守护进程:与终端无关,开机时自动执行,系统关机时 结束。(系统时间、系统中的服务器)。
守护进程(1 int)

3.16、进程的状态
(1)就绪态:进程准备运行
(2)运行态?:进程正在运行
(3)等待态:(休眠态)进程正在等待一件事情或者系统资源
可中断(S):如果进程收到信号会醒来 ctrl+c
不可中断(D):如果进程收到信号不会醒来
(4)停止态(T):进程被中止的状态,还可以重新运行,此时进程被中止SIGSTOP
(5)死亡态(Z):进程被终止(终结),但是task_struct还存在,也就是进程资源还没有回收。、
已终止的进程、僵尸进程
但还在进程向量数组中占有一个task_struct结构
task_struct{
pid_t pid;
R;

};

< 高优先级
N 低优先级
L 有些页被锁
s 会话组组长
+位于前台的进程组
l 多线程,克隆线程

Ctrl + alt +f1 -f6: 打开字符终端
为了不同用户使用计算机
结束字符终端: alt + f7

Ctrl + shift + n:打开当前路径终端

top -p PID: 动态查看进程状态
renice -5 PID: 改变进程的NI值(默认0)
在这里插入图片描述
3.1.8、进程相关的函数
1、创建子进程
#include <sys/types.h>
#include <unistd.h>
函数原型:pid_t fork(void)
/*****************************************
*功能: 创建子进程。
*参数: 无
*
*返回值: 成功 返回值为 0 :表示子进程区域

  • 返回值为 >0 :表示父进程区域
  • 失败 -1,并设置错误信息
    ******************************************/

从fork函数往下分成两个进程开始运行。
父进程和子进程执行顺序的随机的。

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

int main(void)
{
	printf("开始创建进程:\n");

	pid_t pid;	//进程号

	fork();		//创建进程

	while(1)
	{
		printf("abc\n");
		sleep(1);
	}

	return 0;
}
/*
运行结果:每1秒打印2次abc

*/
/*创建一个进程*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

int main(int argc, const char *argv[])
{
	printf("开始创建进程:\n");

	pid_t pid;		//进程号

	pid = fork();		//创建进程

	if(pid == -1)		//-1,失败
	{
		perror("fork");
		exit(-1);
	}
	else if(pid > 0)	//父进程
	{
		while(1)
		{
			printf("father is running\n");
			sleep(3);
		}
	}
	else	//子进程
	{
		while(1)
		{
			printf("son is running\n");
			sleep(3);
		}

	}

	return 0;
}
/*
运行结果:每3秒打印子进程和父进程,父进程和子进程运行先后顺序随机的。

*/

fork函数特性:
(1)、子进程创建时,几乎拷贝了父进程全部内容,包括代码段、数据段、堆栈段、文件描述符、虚拟地址空间
(2)、同一个父进程创建的子进程都是属于同一个进程组 pkill -9 -g PGID
(3)、进程是管理资源的最小单位

思考:会创建多少个进程,进程之间的关系是什么样的?
int main(void)
{
fork();
fork();
}

答:会创建4个多少个进程,关系如下图。
在这里插入图片描述

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

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

	pid1 = fork();	

	if(pid1 < 0)	
	{
		perror("fork");
		exit(-1);
	}
	else if(pid1 > 0)	
	{
		printf("pid1: = %d\n", getpid());
	}
	else
	{
		printf("pid2: = %d\n", getpid());
	}


	pid_t pid2;

	pid2 = fork();	

	if(pid2 < 0)	
	{
		perror("fork");
		exit(-1);
	}
	else if(pid2 > 0)	
	{
		printf("pid3: = %d\n", getpid());
	}
	else
	{
		printf("pid4: = %d\n", getpid());
	}

	while(1);

	return 0;
}
/*
运行结果:创建了4个进程,类似二叉树,如上图。
pid1: = 4786
pid3: = 4786
pid4: = 4788
pid2: = 4787
pid3: = 4787
pid4: = 4789

*/

2、相关考点概念:
僵尸进程: 子进程先于父进程结束,父进程没有回收子进程资源。
孤儿进程: 父进程先于子进程结束,子进程被系统 init.d 托管。

3、在进程中执行另外一个可执行程序,产生新的进程。
原进程中除了进程号,其他的都将被替换。
(1)、
int execl(const char *path, const char *arg, …);
以 l结尾,表示第一个参数必须是 可执行文件名包含路径,后面以列表形式填写参数
第二个参数,必须是可执行程序的 执行命令
第三个参数,可以是 执行命令的 参数选项
最后一个,必须以 NULL 结束

(2)、
int execlp(const char *file, const char *arg, …);
以 lp结尾表示,
第一个参数,可执行文件名,后面以列表形式填写参数,自动搜索文件的路径
第二个参数,必须是可执行程序的 执行命令
第三个参数,可以是 执行命令 的 参数选项
最后一个,必须以 NULL 结束

4、结束进程的函数
(1)、库函数 #include <stdlib.h>
int exit(int status);
结束进程,并且刷新缓冲区

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

void fun()
{
	printf("ccccccccc");

	exit(0); //结束进程  C库函数 刷新缓冲区
}

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

	fun();

	printf("bbbbbb\n");

	return 0;
}
/*
运行结果:
aaaaaa
ccccccccc

*/

(2)、系统调用 #include <unistd.h>
int _exit(int status);
结束进程,不会刷新缓冲区

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

void fun()
{
	printf("ccccccccc");

	_exit(0); //结束进程  系统调用 不会刷新缓冲区
}

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

	fun();

	printf("bbbbbb\n");

	return 0;
}
/*
运行结果:
aaaaaa


*/

5、僵尸进程的解决方法 wait 、 waitpid、信号

(1)、pid_t wait(int *status);
功能: 阻塞父进程,等待任何一个子进程结束,回收资源
头文件:
#include <sys/types.h>
#include <wait.h>
参数:
status: 保存子进程退出时的状态或者 exit函数的实参,
可以使用 固定的宏函数实现(WIFEXITED, WEXITSTATUS)
WEXITSTATUS(status) 获取子进程返回值,

WIFEXITED(status) 判断子进程是否正常结束

返回值:
成功: 返回退出的子进程PID
失败: 返回 -1

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

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

	pid = fork();	

	if(pid < 0)	
	{
		perror("fork");
		exit(-1);
	}
	else if(pid > 0)	//父进程	
	{
		printf("father wait...\n");

		int status;

		//wait(NULL);	//阻塞:可以直接使用这段
		wait(&status);

		printf("%d %d\n", WEXITSTATUS(status), WIFEXITED(status));

		printf("-------------->\n");
		while(1);
	}
	else	//子进程
	{
		sleep(5);
		exit(0);
	}

	return 0;
}	
/*
运行结果:
father wait...
0 1
-------------->

*/

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

父进程先与子进程退出—子进程会变成孤儿进程—被init进程接管(收养)
init进程:系统启动后运行的第一个用户进程,pid=1,会定期扫描系统,收养孤儿进程。
注意:孤儿进程一般没什么危害

(3)、pid_t waitpid(pid_t pid, int *status, int option);
功能:
阻塞父进程,等待子进程结束,回收资源

头文件:
#include <sys/types.h>
#include <wait.h>
参数:
pid : -1, 等价于wait,回收任何一个退出的子进程资源
>0, 为子进程PID, 表示指定要回收的子进程

status: 保存子进程退出时的状态或者 exit函数的实参,
可以使用 固定的宏函数实现(WIFEXITED, WEXITSTATUS)
option: WNOHNG : 非阻塞模式
0 :阻塞模式,等价于 wait
返回值:
成功:
阻塞模式下: 返回退出的子进程PID
非阻塞模式下:返回 0
失败: 返回 -1

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

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

	pid = fork();	

	if(pid < 0)	
	{
		perror("fork");
		exit(-1);
	}
	else if(pid > 0)	//父进程	
	{
		printf("father wait...\n");

		//wait(NULL);	//阻塞
		waitpid(-1, NULL, WNOHANG);	//WNOHANG非阻塞
  						//-1:所有子进程  			

		printf("-------------->\n");
		while(1);
	}
	else	//子进程
	{
		sleep(5);
		exit(10);
	}

	return 0;
}
/*
运行结果:
father wait...
-------------->

*/

5、exec函数簇

(1). 概念:

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

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

(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[]中设置当前进程所使用的环境变量
使用execle和execve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量

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

/*execl	用法*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	int ret = execl("/bin/ls", "ls", "-l", NULL);
	if(ret == -1)
	{
		perror("execl");
		exit(-1);
	}
	
	printf("------->\n");

	return 0;

}
/*
运行结果:
-rwxrwxr-x 1 farsight farsight 7269  7月  6 09:30 a.out
-rw-rw-r-- 1 farsight farsight  617  7月  6 09:30 fork.c


*/
/*execv	用法*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	char *arg[] = {"ls", "-l", NULL};

	int ret = execv("/bin/ls", arg);
	if(ret == -1)
	{
		perror("execv");
		exit(-1);
	}
	
	printf("------->\n");

	return 0;

}
/*
运行结果:
-rwxrwxr-x 1 farsight farsight 7269  7月  6 09:30 a.out
-rw-rw-r-- 1 farsight farsight  617  7月  6 09:30 fork.c


*/
/*execlp	用法*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	int ret = execlp("ls", "ls", "-l", NULL);
	if(ret == -1)
	{
		perror("execlp");
		exit(-1);
	}
	
	printf("------->\n");

	return 0;

}
/*
运行结果:
-rwxrwxr-x 1 farsight farsight 7269  7月  6 09:30 a.out
-rw-rw-r-- 1 farsight farsight  617  7月  6 09:30 fork.c

*/
/*execp	用法*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	char *arg[] = {"ls", "-l", NULL};

	int ret = execvp("ls", arg);
	if(ret == -1)
	{
		perror("execvp");
		exit(-1);
	}
	
	printf("------->\n");

	return 0;

}
/*
运行结果:
-rwxrwxr-x 1 farsight farsight 7269  7月  6 09:30 a.out
-rw-rw-r-- 1 farsight farsight  617  7月  6 09:30 fork.c
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	char *envp[] = {"PATH=xxx", "USER=yyy", NULL};

	int ret = execle("/bin/ls/env", "env", envp);
	if(ret == -1)
	{
		perror("execle");
		exit(-1);
	}
	
	printf("------->\n");

	return 0;

}
/*
运行结果:???????????????
PATH=xxx
USER=yyy

*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	char *arg[] = {"env", NULL};
	char *envp[] = {"PATH=xxx", "USER=yyy", NULL};

	int ret = execve("/usr/bin/env", arg, envp);
	if(ret == -1)
	{
		perror("execve");
		exit(-1);
	}
	
	printf("------->\n");

	return 0;

}
/*
运行结果:
PATH=xxx
USER=yyy

*/
/*my-shell*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

int main(int argc, const char *argv[])
{
	char buf[100];
	char *arg[100];
	int i;
	pid_t pid;

	while(1)
	{
		printf("input>");
		fgets(buf, sizeof(buf), stdin);
		buf[strlen(buf)-1] = '\0';

		if(strncmp(buf, "quit", 4) == 0)
			break;
		arg[0] = strtok(buf, " ");

		i = 1;

		while((arg[i] = strtok(NULL, " ")) != NULL)
		{
			i++;
			pid = fork();
			if(pid == -1)
			{
				perror("fork");
				exit(-1);
			}
			else if(pid == 0)
			{
				int ret = execvp(arg[0], arg);
				if(ret == -1)
				{
					perror("execvp");
					exit(-1);
				}
			}
			else
			{
				wait(NULL);
			}
		}
	}
}
/*
运行结果:可以实现shell
input>	ls-l

*/

6、守护进程daemon

(1).守护进程:
在linux中与用户交互的界面叫终端,从终端运行起来的程序都依附于这个终端,
当终端关关闭时,相应的进程都会被关闭,守护进程可以突破这个限制。

(2).特点:
在后台服务的进程
生存期很长
守护进程独立于控制终端
比如:init进程 pid=1 开机运行 关机才结束

(3).守护进程创建流程:
1. 创建子进程,父进程退出 (摆脱父进程的控制)
fork(void);
2. 在子进程中创建新会话 (拜托终端控制,将孤儿进程独立出来)
setsid(void);
3. 修改工作目录
chdir("");
4. 修改umask (增加安全性)
umask();
5. 关闭文件描述(回收资源)
close();
根据具体功能要求,实现算法

/*创建一个守护进程*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

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

#include <time.h>

int main(int argc, const char *argv[])
{
	pid_t pid;
	pid = fork();

	if(pid == 0)		//子进程
	{
		setsid();	//创建新的会话

		chdir("./");	//修改工作目录

		umask(0);	//设置文件权限掩码,增加安全性

		close(0);	//关闭文件描述符
		close(1);	//关闭文件描述符
		close(2);	//关闭文件描述符

		FILE *fp = fopen("./time.txt", "a");
		while(1)
		{
			time_t t;
			time(&t);

			fprintf(fp, "%s", ctime(&t));

			fflush(fp);

			sleep(2);
		}
	}
	else			//父进程退出
	{
    	exit(0);
	}
}
/*
运行结果:系统不关闭,程序一直运行,可以通过kill杀掉进程
Tue Jul  6 11:13:26 2021
Tue Jul  6 11:13:28 2021
Tue Jul  6 11:13:30 2021
Tue Jul  6 11:13:32 2021
Tue Jul  6 11:13:34 2021
Tue Jul  6 11:13:36 2021

*/

四、线程

4.1、概念:
轻量级的进程,共享同一地址空间的多个任务。

4.2、特点:
1、优点:
1、同一进程中的多个线程,共享该进程地址空间,节约系统空间资源。
2、同一进程中的多线程,进行任务切换时,提高切换的效率,
避免额外的刷新cache和TLB页表的系统时间。
3、同一进程中的多个多线程之间进行数据传递比较方便,可以使用全局变量。

2、缺点:
1、同一进程中的一个线程意外结束或死掉,那么该进程中的其他线程都不能继续运行。
2、同一进程中多个线程,容易竞争共享资源,也就是资源抢占问题。
3、线程所属的进程结束,那么线程就不存在。

4.3、库函数
1、创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t attr,
void * (
routine)(void *), void *arg)

头文件:
#include <pthred.h>
参数:
thread :保存线程id 号的地址
attr :设置线程的属性,通常使用 NULL 缺省属性
routine :线程的执行函数名,执行函数为 void 类型的指针函数
arg :要传递到线程的数据,如果不传数据使用 NULL
返回值:
0 :成功
-1 :失败,并设置错误信息

程序编译时,后面必须 -lpthread
== eg: gcc xianc.c -lpthread==

/*pthread_create 创建线程*/
#include <stdio.h>

//线程函数fun
void *fun(void *arg)
{
    while(1)
    {
     	printf("fun is running\n");
     	sleep(3);       
    }
}

//主线程
int main()
{
  pthread_t tid;//线程号
  int ret = pthread_create(&tid, NULL, fun, NULL);
  if(ret < 0)
  {
    perror("pthread_create");
    return -1;        
  }
  while(1)
  {
       	printf("main is running\n");
     	sleep(3);  
  }
  return 0;
}
/*
运行结果:
main is running
fun is running
main is running
fun is running
main is running
fun is running
main is running
fun is running

*/

回收线程资源
int pthread_join(pthread_t thread, void **retval);
/***************************************
*功能: 阻塞等待指定线程退出,
*接收退出状态并回收子线程资源
*参数:@thread 线程号
*@retval 返回值
*返回值: 成功: 0
*失败: -1 errno值
****************************************/

/*回收线程资源:pthread_join,阻塞*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

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

//主线程
int main()
{
	pthread_t tid;	//线程号
	int ret = pthread_create(&tid, NULL, fun, NULL);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;        
	}
	
	printf("--------------------->\n");

	pthread_join(tid, NULL);	//等待子线程结束,回收资源

	while(1)
	{
		printf("main is running\n");
		sleep(3);  
	}
	return 0;
}
/*
运行结果:
--------------------->
fun is running
fun is running
fun is running
fun is running
fun is running
main is running
main is running
main is running
main is running
main is running
main is running

*/

int pthread_detach(pthread_t thread);
/***************************************
*设置线程为detach,这样线程退出, 资源自动释放,无需使用pthread_join
****************************************/

/*回收线程资源:pthread_detach,非阻塞*/ 
/*工程中常用的,配套的,有创建和系统自动回收资源*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

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

//主线程
int main()
{
	pthread_t tid;	//线程号
	int ret = pthread_create(&tid, NULL, fun, NULL);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;        
	}
	
	printf("--------------------->\n");

	//pthread_join(tid, NULL);	//等待子线程结束,回收资源
	pthread_detach(tid);	//主线程和子线程分离开来,子线程结束,系统自动回收资源,非阻塞

	while(1)
	{
		printf("main is running\n");
		sleep(3);  
	}
	return 0;
}
/*
运行结果:
--------------------->
main is running
fun is running
main is running
fun is running
main is running
fun is running
main is running
fun is running
main is running
fun is running
main is running
main is running
main is running
main is running
main is running

*/

结束子线程
void pthread_exit(void *retval);

/***************************************
*功能:
*参数:@retval 线程退出时返回的值,可被其他线程通过pthread_join获取
*返回值: 无
****************************************/

注意:
pthread_exit == return

线程传参{

地址
}

/*pthread_exit 用法*/
/*地址传递*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

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

	pthread_exit(arg);
}

//主线程
int main()
{
	pthread_t tid;	//线程号

	char a[10] = "hello";

	int ret = pthread_create(&tid, NULL, fun, (void *)a);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;        
	}
	
	void *arg;

	pthread_join(tid, &arg);  //等待子线程结束,回收资源
	printf("arg = %s\n", (char *)arg);

	return 0;
}
/*
运行结果:
fun is running
fun is running
fun is running
arg = hello

*/
/*传参:值传参*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

//线程函数fun
void *fun(void *arg)
{
   printf("%d\n", (int)arg);
	int i = 3;
	while(i--)
	{
		printf("fun is running\n");
		sleep(1);       
	}

	pthread_exit(arg);
}

//主线程
int main()
{
	pthread_t tid;	//线程号

	char a[10] = "hello";
	int b = 100;

	int ret = pthread_create(&tid, NULL, fun, (void *)b);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;        
	}
	
	void *arg;

	pthread_join(tid, &arg);  //等待子线程结束,回收资源
	printf("arg = %d\n", (int)arg);

	return 0;
}
/*
运行结果:
arg = 100
fun is running
fun is running
fun is running
arg = 100

*/

int pthread_cancel(pthread_t thread);
/***************************************
*功能: 在线程中发送关闭指定线程的请求
*参数:@thread 线程号
*返回值: 成功: 0
*失败: 负数,更新 errno
****************************************/

/*pthread_cancel 关闭某个线程的请求*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

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

	pthread_exit(arg);
}

//主线程
int main()
{
	pthread_t tid;	//线程号

	int ret = pthread_create(&tid, NULL, fun, NULL);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;        
	}
	
	pthread_detach(tid);

	char buf[100];
	while(1)
	{
		gets(buf);
		if(strcmp(buf, "q") == 0)
		{
			pthread_cancel(tid);
		}
		if(strcmp(buf, "Q") == 0)
		{
			exit(0);
		}
		printf("buf = %s\n", buf);
	}

	return 0;
}
/*
运行结果:q关闭某个线程的请求,Q退出程序的执行
fun is running
fun is running
fun is running
fun is running
q
buf = q
Q

*/

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

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

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

同步:多个任务按理想的顺序/步调来进行
互斥:不能同时访问

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

线程间的通信机制:
1、同步通信:
多个任务按照某种约定的顺序共同配合的完成一件事情。线程中使用 信号量来实现。

2、信号量:
系统中的资源数量。

3、api接口:
1、初始化信号量
==int sem_init(sem_t *sem, int pshared, unsigned int value) ==
头文件:
#include <semaphore.h>
参数:
sem : 存储信号量的地址
pshared : 信号量的使用范围(0:线程中使用,非0:进程中使用)
value : 信号量的初始值
返回值:
成功: 返回 0
失败: 返回-1

2、p操作,申请资源,相当于消费者
int sem_wait(sem_t *sem);
功能:
申请资源(也就是判断信号量的值)
如果信号量值为 0, 阻塞,当信号量值 不为 0,被唤醒继续运行
如果信号量值为 >0, 非阻塞
资源申请成功,信号量值 -1
参数:
sem : 信号量
返回值:
成功 :0
失败 :-1

3、v操作,释放资源,相当于生产者
int sem_post(sem_t *sem);
功能:
释放资源(也就是将信号量的值 + 1)
如果系统中有等待该资源的任务,就立马唤醒该任务继续运行
如果系统中没有等待该资源的任务,系统中信号量的数值 + 1
参数:
sem :信号量
返回值:
成功: 0
失败:-1

P -1 申请资源 任务继续运行 本来就是0 就会阻塞
V +1 释放资源

/*同步通信,信号量*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <semaphore.h>

sem_t sem;	//信号量

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

	sem_post(&sem);	//信号量+1
}

//主线程
int main()
{
	pthread_t tid;	//线程号

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


	sem_init(&sem, 0, 0);	//信号量初始化,((&sem), (0-线程,非0-进程),(设置处置为0)).

	printf("aaaaaaaaaaaaaa\n");

	sem_wait(&sem);

	printf("bbbbbbbbbbbbbb\n");
	
	pthread_detach(tid);	//信号量-1,遇到0时阻塞

	return 0;
}
/*
运行结果:
aaaaaaaaaaaaaa
fun is running
fun is running
fun is running
fun is running
fun is running
bbbbbbbbbbbbbb

*/
/*输入字符计算字符长度*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

#include <semaphore.h>	//信号量头文件

sem_t sem;	//信号量

char buf[100];	//全局变量

//线程函数fun
void *fun(void *arg)
{
	int i = 5;
	while(1)
	{
		sem_wait(&sem);	//-1,为阻塞
		printf("%d\n", strlen(buf)-1);
	}
}

//主线程
int main()
{
	pthread_t tid;	//线程号

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


	sem_init(&sem, 0, 0);

	do
	{
		fgets(buf, sizeof(buf), stdin);
		sem_post(&sem);	//+1
	}
	while(strncmp(buf, "quit", 4) != 0);

	return 0;
}
/*
运行结果:
hello
5
open
4
haryou
6

*/

4、临界资源:
多个任务共享的全局数据。

5、临界区:
访问操作临界资源的代码行。

6、锁粒度:
互斥锁保护的临界区的大小。临界区越大,锁粒度就越大。

7、保护临界资源的方法:互斥锁
1、互斥锁目的:
保护临界资源,保证数据的完整性,一个线程使用临界资源中,另一个线程就不能使用。
2、api接口:

1、初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) ;
头文件:
#include <pthread.h>
参数:
mutex : 保存互斥锁id 的地址
attr : 互斥锁的属性,一般写 NULL
返回值:
成功:返回 0
失败:返回-1

2、加锁(申请互斥锁)
int pthread_mutex_lock(pthread_mutex_t *mutex)
参数:
mutex :互斥锁
返回值:
成功:返回 0
失败:返回-1

3、解锁锁(释放互斥锁)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
参数:
mutex :互斥锁
返回值:
成功:返回 0
失败:返回-1

3、互斥锁使用时,需要注意:
1、同一临界资源的所有临界区,必须使用同一把互斥锁。

2、每个临界区之前加锁,临界区最后必须解锁,不然会产生死锁。

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

int a = 0, value1, value2; //全局变量

pthread_mutex_t mutex; //互斥锁

void *fun1(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);   //加锁 在全局变量上下写
		if(value1 != value2)
		{
			printf("a=%d value1=%d value2=%d\n", a, value1, value2);
		}
		pthread_mutex_unlock(&mutex); //解锁
	}
}

void *fun2(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);   //加锁 在全局变量上下写
		a++;
		value1 = a;
		value2 = a;
		pthread_mutex_unlock(&mutex); //解锁
	}
}

int main(int argc, const char *argv[])
{
	pthread_t tid1, tid2;

	pthread_mutex_init(&mutex, NULL); //初始化互斥锁
	
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_detach(tid1);

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


	while(1);

	return 0;
}

五、进程间的通信

51、概念:
1、传统进程间通信方式
无名管道、有名管道、信号

2、(System V5)IPC对象通信方式
共享内存、消息队列、信号灯集

3、BSD套接字通信
网络编程 socket 通信

5.2、无名管道:
1、定义
是一个在内核中存在的特殊文件(看不到文件名),使用文件IO进行数据交互。
2、特点:
1、是一种半双工通信(数据只能一个方向传递),有固定的的读端和写端。
2、必须在具有亲缘关系的进程之间使用

3、api接口
1、创建无名管道文件
int pipe(int fd[2]);
头文件:
#include <unistd.h>
参数:
fd :数组名,用来保存读端和写端的文件描述符
返回值:
成功: 返回 0
失败: 返回-1

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

pipefd[0]为读端,pipefd[1]为写端

4、注意:
1、管道中没有数据,读端会阻塞等待,直到有数据
2、读端不存在时,写端写入数据,会收到内核的SIGPIPE信号,终止进程的运行。
3、管道中缓冲区满了,写端写入数据将会阻塞,直到读端读取数据。缓冲区64k = 1024*64

5、无名管道的特点:
a、没有名字,因此无法使用open()打开
b、只能用于亲缘进程间(如父子进程、兄弟进程、祖孙进程等)通信
c、半双工工作方式,读写端是分开的,pipefd[0]为读端,pipefd[1]为写端
d、是一种特殊的文件,只存在内存中,由内核进行管理,随进程生,随进程死
e、对于它的读写可以使用文件IO如read、write函数
f、无名管道的操作属于一次性操作,如果对无名管道进行读操作,数据会被全部读走

6、管道读写注意事项:
1> 当管道中无数据时,执行读操作,读操作阻塞
2>向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
3>对无名管道的操作,类似一个队列,后写入的数据不会覆盖之前的数据,会在其后面存储,读取完的数据会从管道里面移除
4>向无名管道中写数据,将读端关闭,管道破裂,进程收到信号(SIGPIPE),默认这个信号会将进程杀死

单工:固定一种方向进行通信 广播
半双工:方向不定,但是同一时间只能由一端发送到另一端 对讲机
全双工:通信方向都可以,同时可以发送也可以接收 电话

/*一个进程的读写*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

#include <unistd.h>

int main()
{
	int fd[2];	//无名管道

	int ret = pipe(fd);
	if(ret == -1)
	{
		perror("pipe");
		return -1;
	}
	//f[1],写端
	
	write(fd[1], "hello", 5);

	char buf[100] = {0};
    
    //f[0],读端
	read(fd[0], buf, sizeof(buf));

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

	close(fd[0]);
	close(fd[1]);
}
/*
运行结果:
buf = hello

*/
/*一个管道读,一个管道写*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

#include <unistd.h>

int main()
{
	//pipe
	int fd[2];

	int ret = pipe(fd);
	if(ret < 0)
	{
		perror("pipe");
		return -1;
	}

	//fork
	pid_t pid;
	pid = fork();

	if(pid < 0)
	{
		perror("fork");
		return -1;
	}
	else if(pid == 0)	//子进程
	{
		//写,关闭读端
		close(fd[0]);

		write(fd[1], "world\n", 6);

		close(fd[1]);
	}
	else			//父进程
	{
		//读,关闭写端
		close(fd[1]);

		char buf[100] = {0};

		read(fd[0], buf, sizeof(buf));

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

		close(fd[0]);
	}

	return 0;
}
/*
运行结果:
buf = world

*/

4.3、有名管道:
1、定义:
是一种特殊的管道文件,在本地磁盘可见
有名管道也叫命名管道,在本地磁盘可见的管道文件,可以进行文件IO操作。

2、特点:
1、可以在具有亲缘关系的进程间使用,也可以在非亲缘关系的进程中使用。
2、数据遵循先进先出
3、可以使用文件IO中除了lseek之外的函数操作

3、api接口
1、创建管道文件
int mkfifo(const char *filename, mode_t mode);
头文件:
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
参数:
filename: 要操作的管道文件名(可包含路径)
mode: 8进制的权限
返回值:
成功: 返回0
失败: 返回-1

在shell中使用mkfifo命令: mkfifo filename

eg: mkfifo f1

或者

if(mkfifo(“f1”,0666) == -1)
{
perror("mkfifo ");
return -1;
}

==4、注意: ==
1、有名管道文件的数据交互在 内核中,本地磁盘文件中没有数据
2、有名管道的读进程结束,写进程写入数据会终止程序运行
3、有名管道的写进程结束,读进程会一直运行,读到0直接数据

信号处理原型
void(*signal(int signum,void(*handler)(int)))(int)

5、有名管道和无名管道的异同点
1、相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。
读写数据都是在给内存的操作,并且都是半双工通讯。
2、区别
有名在任意进程之间使用,无名在父子进程之间使用

//亲缘进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
	int ret = mkfifo("f1", 0666);
	if(ret < 0)
	{
		perror("mkfifo");
		return -1;
	}

	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork");
		exit(-1);
	}
	else if(pid == 0) //子进程
	{
		int fd = open("f1", O_WRONLY);

		write(fd, "hello\n", 6);

		close(fd);
	}
	else{ //父进程
	
		int fd = open("f1", O_RDONLY);

		char buf[100] = {0};

		read(fd, buf, sizeof(buf));

		printf("buf = %s", buf);

		close(fd);
	}

	return 0;
}
/*非亲缘进程*/
/*fifo_write.c*/
#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 0
	int ret = mkfifo("f2", 0666);
	if(ret < 0)
	{
		perror("mkfifo");
		return -1;
	}
#endif
	int fd = open("f2", O_WRONLY);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	while(1)
	{
		printf("input>");
	
		char buf[100] = {0};

		fgets(buf, sizeof(buf), stdin);
	
	
		write(fd, buf, strlen(buf));

		if(strcmp(buf, "quit\n") == 0)
			break;
	}


	return 0;
}

/*fifo_read.c*/
#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 0
	int ret = mkfifo("f2", 0666);
	if(ret < 0)
	{
		perror("mkfifo");
		return -1;
	}
#endif
	int fd = open("f2", O_RDONLY);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	printf("wait a write......\n");

	while(1)
	{	
		char buf[100] = {0};
		
		int n = read(fd, buf, sizeof(buf));
		if(n <= 0)
		{
			break;
		}

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

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

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

	printf("over\n");

	return 0;
}

数据传输特点:
1、读端不存在时,写端写入数据将会阻塞
2、读端意外结束,写端再写数据将会管道破裂,该进程结束

3.信号

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

#include <signal.h>

int kill(pid_t pid, int signo); //kill把信号发送给进程或进程组;
int raise(int signo); //raise把信号发送给(进程)自身.

返回值:成功则返回0,
出错则返回-1

特点:
raise(signo); 等价于 kill(getpid(), signo);

//结束一个进程

alarm();//设置闹钟
pause();//程序暂停

可以为当前进程定义闹钟,时间到了会发出SIGALRM信号。
每个进程只能有一个alarm,当重新定义时,会重新计时。
如果之前定义了一个闹钟,则这次定义返回的是上次闹钟剩余的时间,否则返回0.

pause函数的作用,是让当前进程暂停运行,交出CPU给其他进程去执行;
当前进程进入pause状态后,当前进程会表现为“卡住、阻塞住”;
要退出pause状态,当前进程需要被信号唤醒。

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

int main(int argc, const char *argv[])
{
	alarm(5);		//5秒之后会发出一个SIGALRM信号

	sleep(2);		//睡眠2秒

	int ret = alarm(5);	//返回上一次闹钟剩余时间3s

	printf("%d\n", ret);	//返回3s

	pause();		//让程序暂停

	printf("------------>\n");

	while(1);

	return 0;
}
/*
运行结果:
3
Alarm clock

*/

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

sighandler_t signal(int signum, sighandler_t handler);
参数:
/********************************
*signum: 捕获信号,设置信号的处理方式
*handler: SIG_IGN:忽略
*SIG_DFL:默认
*自定义的信号处理函数
*********************************/

SIGINT : CTRL + C
SIGQUIT : CTRL +
SIGTSTP : CTRL + Z
SIGKILL : 立即结束进程
SIGALRM : 当一个定时器结束时发出
SIGSTOP : 暂停一个进程

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数:只要产生了SIGQUIT\SIGTSTP信号,都会执行处理函数
void handler(int signum)	//信号处理函数
{
	if(signum == SIGQUIT)	//ctrl + '\'
	{
		printf("笑脸\n");
	}
	else if(signum == SIGTSTP)//ctrl + 'z'
	{
		printf("哭脸\n");
	}
}

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

	//signal(SIGINT, SIG_IGN);	//忽略ctrl + c
	
	//signal(SIGINT, SIG_DFL);	//默认ctrl + c可以停止运行

	signal(SIGQUIT, handler);	//自定义,注册
	
	signal(SIGTSTP, handler);	//自定义,注册   

	while(1)
	{
		printf("hello word\n");
		sleep(2);
	}

	return 0;
}
/*
运行结果:
hello word
^Z哭脸
hello word
^\笑脸
hello word
^\笑脸
hello word
^\笑脸
hello word

*/

共享内存:
共享内存是一种最为高效的进程通信方式,而不需要任何数据拷贝。

步骤:
1、ftok
2、shmget
3、shmat
4、进程间的通信
5、shmdt
6、shmctl

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

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

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

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

#include <unistd.h>

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

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

int main(void)
{
	key_t key;

	//1.得到key值
	key = ftok("./", 1);
	if(key == -1)
	{
		perror("ftok");
		return -1;
	}
	printf("%#x\n", key);

	//2.创建共享内存
	int shmid = shmget(key, 1024, IPC_CREAT | 0666);
	if(shmid == -1)
	{
		perror("shmget");
		return -1;
	}
	printf("shmid = %d\n", shmid);
	
	system("ipcs -m");

	//3.映射
	char *p = shmat(shmid, NULL, 0);
	if(NULL == p)
	{
		perror("shmat");
		goto xxx;
	}

	//4.fork
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork");
		goto xxx;
	}
	else if(pid == 0)//子进程,写入数据
	{
		while(1)
		{
			char buf[32] = {0};

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

			strcpy(p, buf);	//往共享内存写入数据

			if(strncmp(p, "quit", 4) == 0)
				break;
		}
	}
	else		//父进程,读取共享内存区数据
	{
		waitpid(pid, NULL, WNOHANG);	//非阻塞等待
		
		while(1)
		{
			printf("p = %s\n", p);

			sleep(3);

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

	//5.解除映射
	int ret = shmdt(p);
	if(ret == -1)
	{
		perror("shmdt");
		goto xxx;
	}	

	//6.删除共享内存
	ret = shmctl(shmid, IPC_RMID, NULL);
	{
		if(ret == -1)
		{
			perror("shmctl");
			goto xxx;
		}

	}

xxx:
	sleep(2);	//防止出错
	char buf[32] = {0};
	sprintf(buf, "ipcrm -m %d", shmid);

	system(buf);

	system("ipcs -m");

	return 0;
}
/*
运行结果:
hello
p = hhello

p = hhello

p = hhello

p = hhello

p = hhello



*/

消息队列
步骤:
1、ftok
2、msgget
3、进程间的通信
4、msgsnd
5、magrcv
6、msgctl

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

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

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

#include <unistd.h>

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

struct msgbuf{
	long mtype;	//消息类型
	char buf[100];	//消息正文
};

int main(void)
{
	key_t key;

	//1.得到key值
	key = ftok("./", 'a');
	if(key == -1)
	{
		perror("ftok");
		return -1;
	}
	printf("%#x\n", key);

	//2.创建消息队列
	int msgid = msgget(key, IPC_CREAT | 0666);
	if(msgid == -1)
	{
		perror("msgget");
		goto xxx;
	}
	printf("msgid = %d\n", msgid);
	
	system("ipcs -q");

	//3.fork
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("fork");
		goto xxx;
	}
	else if(pid == 0)//子进程,写入数据
	{
		while(1)
		{
			char buf[32] = {0};

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

			struct msgbuf msg;	//结构体变量

			msg.mtype = 100;	//消息类型
			strcpy(msg.buf, buf);	//消息正文

			//发送消息,将消息添加到消息队列
			msgsnd(msgid, &msg, sizeof(msg)-sizeof(long), 0);

			if(strncmp(msg.buf, "quit", 4) == 0)
				break;
		}
	}
	else		//父进程,
	{
		waitpid(pid, NULL, WNOHANG);	//非阻塞等待
		
		while(1)
		{
			struct msgbuf msg;

			msgrcv(msgid, &msg, sizeof(msg)-sizeof(long), 100, 0);
			printf("msg.buf = %s\n", msg.buf);

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

	//6.删除消息队列
	int ret = msgctl(msgid, IPC_RMID, NULL);

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

xxx:
	sleep(2);	//防止出错
	char buf[32] = {0};
	sprintf(buf, "ipcrm -q %d", msgid);

	system(buf);

	system("ipcs -q");

	return 0;
}
/*
运行结果:
ok
msg.buf = ok

open
msg.buf = open

look
msg.buf = look

quit
msg.buf = quit




*/

信号灯集
步骤:
1、ftok
2、shmget
3、shmat
4、semget
5、semctl
6、进程间的通信
5、
7、shmctl

跳转:IO进线程概念理论!

跳转:IO进线程概念理论!

跳转:下一篇,网络编程总结!

跳转:下一篇,网络编程总结!

跳转:开头

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 17:01:25  更:2021-07-31 17:01:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 23:04:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码