Linux系统之I/O进程

Linux系统之I/O进程

1. C库和C程序的编译过程

1.1 C库的分类

1.1.1 静态库的概念

  1. 静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
  2. 静态库文件以 “.a” 结尾

1.1.2 制作静态库

  1. 制作静态库的命令
gcc -c fun.c -o fun.o

静态库以== lib ==开头,中间是静态库的名字,以 .a 结尾

ar -crs libfun.a fun.o
  1. 使用静态库
gcc main.c -L ./ -lfun -o main

1.1.3 动态库的概念

  1. 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
  2. 使用动态库的优点是系统只需载入一次动态库,不同的程序可以得到内存中相同的动态库的副本,因此节省了很多内存。
  3. 静态库文件以 “.so” 结尾

1.1.4 制作动态库

  1. 制作动态库的命令
gcc -fpic fun.c -c -o fun.o

动态库以 lib 开头,中间是动态库的名字,以 .so 结尾

gcc fun.o shared -o libmyfun.so
  1. 使用动态库
gcc main.c -L. -libmyfun -o hello

直接编译会导致./main: error while loading shared libraries: libmyfun.so: 
			cannot open shared object file: No such file or directory

解决方法:把libfund.so动态库文件放入/lib目录或/usr/lib目录,就可以执行了
		可以直接使用 gcc main.c -libmyfun -o main 编译

1.2 编译过程(共四步)

预处理:gcc -E **.c -o *.i
	   原文件——预处理文件
编译:  gcc -S **.i -o *.s
	   预处理文件——汇编文件
汇编:  gcc -c **.s -o *.o
	   汇编文件——二进制文件
链接:  gcc **.c -o *.elf
	   二进制文件——可执行文件

2. 标准I/O

2.1 文件类型

  1. 磁盘文件:指的是一组相关数据有序的集合,通常存储在外部介质上(磁盘),使用时候才调入内存。
  2. 设备文件:在操作系统中把每一个与主机相连输入输出设备看作文件,把他们输入输出同等与磁盘文件的读和写。
  3. 计算机的存储在物理上是二进制,所以物理上所有的磁盘文件本质上都是一样,以字节为单位进行顺序存储。

文本文件:基于字符编码的文件,常见编码ASCII美国信息交换标准代码,Unicode等。
存储的是字符数据,也就是将每一个字符的ASCII码进行存储。

二进制文件:基于值编码的文件,所有的数字,都是以整形数据进行存储,常见的有音频文件、视频文件等。

2.2 文件缓冲区(ANSI C采用缓冲文件系统处理数据文件)

2.2.1 全缓冲4096

  1. 清除缓冲区的方式
  1. 当缓冲满时。
  2. 程序正常结束时。
  3. 关闭系统时。
  4. 使用刷新函数 int fflush(FILE *stream)

2.2.2 行缓冲1024

  1. 清除缓冲区的方式
  1. 当缓冲满时。
  2. 程序正常结束时。
  3. 关闭系统时。
  4. 使用刷新函数 int fflush(FILE *stream)
  5. 遇到 \n
  1. 进程刚刚启动的时候,系统会我们自动打开两个文件,分别是 FILE * stdin 终端输入FILE * stdout 终端输出

2.2.3 无缓冲1

进程刚刚启动的时候,系统会我们自动打开一个文件,FILE * stderr 终端错误输出

2.3 标准I/O相关函数

Mode(方式) 意义 
	"r" 	只读方式打开
	"w" 	只写方式打开,如果文件存在就清空文件,如果文件不存在就创建一个文件
	"a" 	以追加方式打开,在末尾添加内容,如果文件不存在就创建文件

	"r+" 	以读写方式打开文件,如果不存在不会创建
	"w+" 	以读写方式打开文件,如果存在就清空,不存在就创建
	"a+" 	以读写方式打开文件,如果不存在就创建,如果存在就追加到尾部 
************************************************************************************
	"rb" 	打开一个用于读取的二进制文件 	(只读二进制文件)
	"wb" 	创建一个用于写入的二进制文件 	(没有文件会创建文件,只写)
	"ab" 	追加到一个二进制文件 

	"rb+" 	打开一个用于读/写的二进制文件 
	"wb+" 	创建一个用于读/写的二进制文件 
	"ab+" 	打开一个用于读/写的二进制文件 

2.3.1 打开文件——fopen()函数

#include <stdio.h>
FILE * fopen(const char *pathname, const char *mode);
/*********************************************************************************  
* Description:  打开文件
* Input:         pathname:文件名,mode:模式
* Return:        成功:一个关联该文件的结构体(流)
* 				 失败:NULL
* Others:        
**********************************************************************************/

2.3.2 关闭文件——fclose()函数

#include <stdio.h>
int fclose( FILE *stream );
/*********************************************************************************  
* Description:  关闭文件
* Input:         stream:文件流指针
* Return:        成功:0
* 				 失败:EOF
* Others:        
**********************************************************************************/

2.3.3 打印错误信息——perror()函数

#include <stdio.h>
void perror( const char *str );
/*********************************************************************************  
* Description:  打印str(字符串)和一个相应的执行定义的错误消息
* Input:         想要打印的str(字符串)
* Return:        
* Others:        主要还是用于提示信息
**********************************************************************************/

2.3.4 检查文件是否到文件结尾——feof()函数

#include <stdio.h>
int feof( FILE *stream );
/*********************************************************************************  
* Description:  在到达给出的文件流的文件尾时,返回一个非零值
* Input:         stream:文件流指针
* Return:        返回一个非零值
* 				 
* Others:        
**********************************************************************************/

2.3.5 文件随机存取——fseek()函数

#include <stdio.h>
int fseek( FILE *stream, long offset, int origin );
/*********************************************************************************  
* Description:  为文件流设置位置数据
* Input:         stream:文件流指针, offset:偏移量, origin从哪里开始读取
* 				 origin:
* 				  名称 		        说明 
*				 SEEK_SET 	从文件的开始处开始搜索 
*				 SEEK_CUR 	从当前位置开始搜索 
*				 SEEK_END 	从文件的结束处开始搜索 
* Return:        成功:0
* 				 失败:EOF
* Others:        偏移量:往前偏移就是-1、-2...,往后偏移就是1、2...
**********************************************************************************/

示例:

/******************************
    > File Name    : 3.c
    > Author       : GK
    > Description  : 在文件中重新设定新的位置,将剩余的内容打印出来
    > Created Time : 2020年11月01日 星期日 14时04分58秒
 *****************************/
//test.txt 文件中的内容是:hello world\n

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

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

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

	if(fseek(fp, 3, SEEK_SET) == EOF)
	{
		perror("feek");
		return -2;
	}

	while(1)
	{
		int ch = fgetc(fp);
		if( feof(fp) )
		{
			break;
		}

		printf("%c", ch);
	}

	if( fclose(fp) == EOF)
	{
		perror("fclose");
		return -3;
	}

	return 0;

}

终端运行结果:

gaokuo@Linux:~/Public/8练习$ make 3
cc     3.c   -o 3
gaokuo@Linux:~/Public/8练习$ ./3
lo world

2.3.6 按照格式去写文件——fprintf()函数

#include <stdio.h>
int fprintf( FILE *stream, const char *format, ... );
/*********************************************************************************  
* Description:  根据指定的format(格式)(格式)发送信息(参数)到由stream(流)指定的文件
* Input:         stream:文件流指针,format:输出格式
* Return:        成功:输出的字符数
* 				 失败:-1
* Others:        fprintf()只能和printf()一样工作
**********************************************************************************/

2.3.7 按照字符读写文件——fgetc()函数和fputc()函数

#include <stdio.h>
int fgetc( FILE *stream );
/*********************************************************************************  
* Description:  按照字符读文件
* Input:         stream:文件流指针
* Return:        成功:来自stream(流)中的下一个字符
* 				 失败或到达文件末尾:EOF
* Others:        
**********************************************************************************/

int fputc( int ch, FILE *stream );
/*********************************************************************************  
* Description:  按照字符写文件
* Input:         stream:文件流指针  ch:要写入的字符
* Return:        成功:字符
* 				 失败:EOF
* Others:        
**********************************************************************************/

示例:

/******************************
    > File Name    : 1.c
    > Author       : GK
	> Description  : 在文件中写入 hello world
    > Created Time : 2020年11月01日 星期日 13时32分07秒
 *****************************/
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char buf[] = "hello world\n";
	FILE *fp = fopen("test.txt", "w");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	puts("fopen is OK!");

	for(int i=0; i<strlen(buf); i++)
	{
		fputc(buf[i], fp);
	}

	int ret = fclose(fp);
	if(ret == EOF)
	{
		perror("fclose");
		return -2;
	}
	
	puts("fclose is OK!");
	return 0;

}

终端运行结果:

gaokuo@Linux:~/Public/8练习$ make 1
cc     1.c   -o 1
gaokuo@Linux:~/Public/8练习$ ./1
fopen is OK!
fclose is OK!
gaokuo@Linux:~/Public/8练习$ ls
1  1.c  test.txt
/******************************
    > File Name : 1.c
    > Author: GK
    > Description  : 读出文件中的内容
    > Created Time : 2020年11月01日 星期日 13时32分07秒
 *****************************/
#include <stdio.h>
#include <string.h>

//读出文件中的内容
int main(int argc, char *argv[])
{
	char buf[] = {0};
	int ch=0;

	FILE *fp = fopen("test.txt", "r");
	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	puts("fopen is OK!");

	while(1)
	{
		ch = fgetc(fp);
		if( feof(fp) )
			break;

		printf("%c", ch);	
	}

	int ret = fclose(fp);
	if(ret == EOF)
	{
		perror("fclose");
		return -2;
	}
	
	puts("fclose is OK!");
	return 0;

}

终端运行结果:

gaokuo@Linux:~/Public/8练习$ make 2
cc     2.c   -o 2
gaokuo@Linux:~/Public/8练习$ ./2
fopen is OK!
hello world
fclose is OK!

2.3.8 按照行读写文件——fgets()函数和fputs()函数

#include <stdio.h>
char *fgets( char *str, int num, FILE *stream );
/*********************************************************************************  
* Description:  按照行读文件 
* Input:         stream:文件流指针  str:读出字符串缓冲区   num: 读出多少字符
* Return:        成功:返回str(字符串)
* 				 失败:NULL
* Others:        从给出的文件流中读取[num - 1]个字符,并且把它们转储到buf中
**********************************************************************************/

int fputs( const char *str, FILE *stream );
/*********************************************************************************  
* Description:  按照行写文件
* Input:         stream:文件流指针  str:要写入的字符串
* Return:        成功:非负值
* 				 失败:EOF
* Others:        
**********************************************************************************/

示例:

/******************************
    > File Name    : 4.c
    > Author       : GK
    > Description  : 使用fgets函数,在test1.txt文件中添加内容
    > Created Time : 2020年11月01日 星期日 14时32分15秒
 *****************************/
#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[])
{
	char str[32] = {0};

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

	for(int i=0; i < 3; i++) // 写入三行
	{
		sprintf(str, "%d: This is an anple\n", i+1);
		if( fputs(str, fp) == EOF)
		{
			perror("fputs");
			break;
		}
	}

	if( fclose(fp) == EOF)
	{
		perror("fclose");
		return -2;
	}

	return 0;

}

终端运行结果:

gaokuo@Linux:~/Public/8练习$ make 4
cc     4.c   -o 4
gaokuo@Linux:~/Public/8练习$ ./4
gaokuo@Linux:~/Public/8练习$ cat test1.txt
1: This is an anple
2: This is an anple
3: This is an anple
/******************************
  > File Name    : 5.c
  > Author       : GK
  > Description  : 从 test1.txt文件中,并且定位到第二行,打印第二行的内容
  > Created Time : 2020年11月01日 星期日 14时48分46秒
 *****************************/
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
	FILE *fp = fopen("test1.txt", "r");
	char buf[32] = {0};

	if(fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	puts("fopen is OK!");

	fgets(buf, sizeof(buf)-1, fp);
	
	if(fseek(fp, strlen(buf), SEEK_SET) == EOF)
	{
		perror("fseek");
		return -2;
	}

	fgets(buf, sizeof(buf)-1, fp);
	printf("buf: %s",buf);

	if(fclose(fp) == EOF)
	{
		perror("fclose");
		return -3;
	}
	puts("fclose is OK!");

	return 0;

}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 5
cc     5.c   -o 5
gaokuo@Linux:~/Public/8练习$ ./5
fopen is OK!
buf: 2: This is an anple
fclose is OK!

2.3.9 按照块读写文件——fread()函数和fwrite()函数

#include <stdio.h>
int fread( void *buffer, size_t size, size_t num, FILE *stream );
/*********************************************************************************  
* Description:  按照块读文件,读的是二进制文件
* Input:         buffer:缓冲区,size:一块的大小,count:多少块,stream:文件流指针
* Return:        成功:读取的内容数量
* Others:        读取[num]个对象(每个对象大小为size(大小)指定的字节数),并把它们替换到由buffer(缓冲区)指定的数组
**********************************************************************************/

int fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
/*********************************************************************************  
* Description:  按照块写文件,写的也是二进制文件
* Input:         buffer:缓冲区,size:一块的大小,count:多少块,stream:文件流指针
* Return:        成功:已写的对象的数量
* Others:        从数组buffer(缓冲区)中, 写count个大小为size(大小)的对象到stream(流)指定的流
**********************************************************************************/

示例:

/******************************
    > File Name    : 6.c
    > Author       : GK
    > Description  : 使用fwrite函数写入二进制文件中
    > Created Time : 2020年11月01日 星期日 15时20分03秒
 *****************************/
#include <stdio.h>
#include <string.h>

typedef struct{
	char name[30];
	int id;
}stu;

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

	puts("请输入信息(姓名):");
	stu s[3];
	for(int i=0; i<3; i++)
	{
		scanf("%s", s[i].name);
		s[i].id = i;
	}
	fwrite(s, sizeof(stu), 3, fp);

	if(fclose(fp) == EOF)
	{
		perror("fclose");
		return -2;
	}

	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 6
cc     6.c   -o 6
gaokuo@Linux:~/Public/8练习$ ./6
请输入信息(姓名):
张三
李四
王五
gaokuo@Linux:~/Public/8练习$ cat 1.txt 
张三Genu李四��~h�Q�����王五��\��~9�Ugaokuo@Linux:~/Public/8练习$ 

/*因为fwrite函数写入的是二进制文件,所以除了名字是字符外,其余均是乱码,很正常*/
/******************************
    > File Name    : 7.c
    > Author       : GK
    > Description  : 使用fread函数读出二进制文件的内容
    > Created Time : 2020年11月01日 星期日 15时37分07秒
 *****************************/
#include <stdio.h>
typedef struct{
	char name[30];
	int id;
}stu;

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

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

	stu s[3];
	if( fread(s, sizeof(stu), 3, fp) == 0)
	{
		puts("fread");
		return -2;
	}

	for(int i=0; i<3; i++)
	{
		printf("name = %s id = %d\n", s[i].name, s[i].id);
	}
	

	if( fclose(fp) == EOF )
	{
		perror("fclose");
		return -3;
	}
	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 7
cc     7.c   -o 7
gaokuo@Linux:~/Public/8练习$ ./7
name = 张三 id = 0
name = 李四 id = 1
name = 王五 id = 2

3. 文件I/O

文件IO

1. 不带缓冲区
2. Linux系统提供给我们的函数,直接调用系统调用(man手册第二章)
3. 通过文件描述符来操作文件

文件描述符

标准IO通过fopen获取FILE的流指针,FILE就描述了这个文件。
​ open获取一个int类型的数值,这个数值范围0~1023

​ 其中有三个特殊的文件描述符
​ 0:标准输入文件描述符
​ 1:标准输出文件描述符
​ 2:标准错误文件描述符

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

int main(int argc, char *argv[])
{
  	printf("stdin 的文件描述符是:%d\n",stdin->_fileno);
  	printf("stdout的文件描述符是:%d\n",stdout->_fileno);
  	printf("stderr的文件描述符是:%d\n",stderr->_fileno);
  	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 8
cc     8.c   -o 8
gaokuo@Linux:~/Public/8练习$ ./8
stdin 的文件描述符是:0
stdout的文件描述符是:1
stderr的文件描述符是:2
标准I/O文件I/O
rO_RDONLY
r+O_RDWR
wO_WRONLY | O_CREAT | O_TRUNC
w+O_RDWR | O_CREAT | O_TRUNC
aO_WRONLY | O_APPEND | O_CREAT
a+O_RDWR | O_APPEND | O_CREAT

3.1 文件I/O相关函数

3.1.1 打开文件——open()函数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>
void open( const char *filename );
/*********************************************************************************  
* Description:  打开文件
* Input:         stream:文件流指针
* Return:        成功:	0:标准输入文件描述符
*						1:标准输出文件描述符
*						2:标准错误文件描述符
*				 失败: 	-1
* Others:        
**********************************************************************************/

3.1.2 关闭文件——close()函数

#include <unistd.h.h>
int close(int fd);
/*********************************************************************************  
* Description:  关闭文件
* Input:         fd:文件描述符
* Return:        成功:	0
*				 失败: 	-1
* Others:        
**********************************************************************************/

3.1.3 写文件——write()函数

#include <unistd.h.h>
ssize_t write(int fd, const void *buf, size_t count);
/*********************************************************************************  
* Description:  打开文件
* Input:         fd:文件描述符,buf:存储缓冲区,count:写入大小
* Return:        成功:写入字节数
* 				 失败:-1
* Others:        将buf中的数据写入fd文件中
**********************************************************************************/

示例:

/******************************
  > File Name    : 9.c
  > Author       : GK
  > Description  : 向2.txt文件中写入字符串
  > Created Time : 2020年11月01日 星期日 16时23分29秒
 *****************************/
#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[])
{

	int fd = open("2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0777);
	if(fd == -1)
	{
		perror("open");
		return -1;
	}

	char buf[32] = "This is an anple\n";
	int re = write(fd, buf, strlen(buf));
	if(re == -1)
	{
		perror("write");
		return -3;
	}else if(re > 0)
	{
		printf("写入了%d个数据\n",re);
	}

	if(close(fd) == -1)
	{
		perror("close");
		return -2;
	}
	return 0;

}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 9
cc     9.c   -o 9
gaokuo@Linux:~/Public/8练习$ ./9
写入了17个数据

3.1.4 读文件——read()函数

#include <unistd.h.h>
ssize_t read(int fd, void *buf, size_t count);
/*********************************************************************************  
* Description:  读文件
* Input:         fd:文件描述符,buf:存储缓冲区,count:读出大小
* Return:        成功:读取字节数,0表示读完了
* 				 失败:-1
* Others:        将fd文件中的内容读出到buf中
**********************************************************************************/

示例:

/******************************
  > File Name    : 10.c
  > Author       : GK
  > Description  : 将文件2.txt文件中的内容读出来
  > Created Time : 2020年11月01日 星期日 16时23分29秒
 *****************************/
#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[])
{

	int fd = open("2.txt", O_RDONLY);
	if(fd == -1)
	{
		perror("open");
		return -1;
	}

	char buf[32] = {0};
	int re = read(fd, buf, sizeof(buf)-1);
	if(re == 0)
	{
		puts("读到文件的末尾!");
	}else if(re == -1)
	{
		perror("read");
	}else
	{
		printf("buf: %s\n", buf);
	}
	
	if(close(fd) == -1)
	{
		perror("close");
		return -2;
	}
	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ vim 10.c
gaokuo@Linux:~/Public/8练习$ make 10
cc     10.c   -o 10
gaokuo@Linux:~/Public/8练习$ ./10
buf: This is an anple

3.1.5 文件内容定位——lseek()函数

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*********************************************************************************  
* Description:  文件定位
* Input:         fd:文件描述符,offset:偏移量,
* 				 whence:从哪里开始
* 					SEEK_SET:开头
* 					SEEK_CUR:当前位置
* 					SEEK_END:末尾
* Return:        成功:返回设置指针偏移后的位置
* 				 失败:-1
* Others:        
**********************************************************************************/

示例:

/******************************
  > File Name    : 11.c
  > Author       : GK
  > Description  : 
  > Created Time : 2020年11月01日 星期日 18时46分20秒
 *****************************/
 /** 2.txt文件中的内容
 *	This is an anple
	This is a book
	This is a table
 */
#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[32] = "";
	int fd = open("2.txt", O_RDONLY);
	if (fd == -1) 
	{
		perror("open");
		return -1;
	}
	read(fd, buf, sizeof(buf)-1);

	int ret = lseek(fd, strlen(buf), SEEK_SET);
	if (ret == -1) 
	{
		perror("lseek");
		return 2;
	}

	ret = read(fd, buf, sizeof(buf));
	if (ret < 0) 
	{
		perror("read");
		return -2;
	}else if(ret == 0)
	{                         
		perror("read");
		return -3;
	}else
	{
		puts(buf);
	}

	close(fd);
	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 11
cc     11.c   -o 11
gaokuo@Linux:~/Public/8练习$ ./11

This is a table
This is a book

3.1.6 查看文件属性——stat()函数

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
/*********************************************************************************  
* Description:  查看文件属性
* Input:         pathname:文件名,statbuf:文件属性结构体
* Return:        成功:0
* 				 失败:-1
* Others:        将文件的属性保存到statbuf结构体中
**********************************************************************************/

3.2 查看用户属性的相关函数

3.2.1 获取uid用户的ID——getuid()函数

#include <sys/types.h>
#include <unistd.h>
uid_t getuid(void);
/*********************************************************************************  
* Description:  获取uid用户的ID
* Input:         无
* Return:        用户的ID
* Others:        
**********************************************************************************/

3.2.2 获取uid用户的passwd信息——getpwuid()函数

#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
/*********************************************************************************  
* Description:  获取uid用户的passwd信息
* Input:         uid:用户的ID
* Return:        passwd的结构体信息
* Others:        
**********************************************************************************/

3.2.3 获取gid组的组名——getgrgid()函数

#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
/*********************************************************************************  
* Description:  获取gid组的组名
* Input:         gid:组ID
* Return:        group的结构体信息
* Others:        
**********************************************************************************/

3.3 获取时间的函数

3.3.1 获取时间(秒)——time()函数

#include <time.h>
time_t time(time_t *tloc);
/*********************************************************************************  
* Description:  打开文件
* Input:         tloc:秒的变量值
* Return:        成功:时间值参数
* 				 失败:-1
* Others:        
**********************************************************************************/

3.3.2 获取本地时间——localtime()函数

#include <time.h>
struct tm *localtime(const time_t *timep);
/*********************************************************************************  
* Description:  函数转换参数time为本地时间格式
* Input:         timep:传入获取到的秒值
* Return:        成功:返回关于tm的时间结构体
* 				 失败:-1
* Others:        
**********************************************************************************/

3.3.3 获取本地时间——ctime()函数

#include <time.h>
char *ctime( const time_t *time );
/*********************************************************************************  
* Description:  函数转换参数time为本地时间格式
* Input:         timep:传入获取到的秒值
* Return:        成功:返回关于time的字符串
* Others:        
**********************************************************************************/

示例:

/******************************
    > File Name    : 12.c
    > Author       : GK
    > Description  : 时间函数综合示例程序
    > Created Time : 2020年11月01日 星期日 19时00分29秒
 *****************************/
#include <stdio.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char *argv[])
{
	time_t secs = time(NULL);
	struct tm *ret = NULL;

	ret = localtime(&secs);

	printf("%02d-%02d-%02d\n",ret->tm_year+1900,ret->tm_mon+1,ret->tm_mday);
	printf("%02d:%02d:%02d\n",ret->tm_hour,ret->tm_min,ret->tm_sec);
	printf("从1970年一月一日到现在秒数为:%ld\n",secs);

	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 12
cc     12.c   -o 12
gaokuo@Linux:~/Public/8练习$ ./12
2020-11-01
19:05:191970年一月一日到现在秒数为:1604228719

4. 进程

4.1 进程相关概念

4.1.1 进程和程序的区别

程序:

  1. 程序是静态文件。
  2. 它是程序执行的过程,包括创建、调度和消亡。

进程:

  1. 是程序的一次执行过程,进程是动态文件。
  2. 它是运行着的实体,它是程序执行的过程,包括创建、调度和消亡。
  3. 进程是一个独立的可调度的程序。

4.1.2 小的知识点

  1. PID是父进程的ID号,PPID是子进程的ID号
  2. 进程的状态:运行态R等待态S停止态T僵尸态Z
  3. 子进程先于父进程结束,子进程–>僵尸进程
  4. 父进程先于子进程结束,子进程–>孤儿进程
  5. 进程之间的相互管理关系:进程——>进程组——>会话组

4.1.3 进程的相关命令

  1. 查看进程之间的关系:
pstree
  1. 查看父进程号(PPID)和子进程号(PID):
ps -ef | grep "a.out"
  1. 杀死进程:
kill signum pid
kill -l 查看signum的属性
kill pid 默认的效果就是结束进程
kill 其实的给进程发信号
  1. 动态显示进程:
top
  1. 改变正在运行进程的优先级:
renice -n 优先级号 PID
  1. 按用户指定进程的优先级去启动:
nice -n 优先级号 PID
nice (-2019)数字越小,优先级越高。
  1. 查看终端下的正在工作的作业:
jobs 
  1. 将挂起的进程在后台执行:
第一步:Ctrl + z 让前台正在运行的进程挂起 
第二步:bg 作业号 将挂起的进程 放到后台执行
第二步:bg 作业号 将挂起的进程 放到后台执行
  1. 把后台运行的进程放到前台运行:
 fg 作业号
  1. 后台进程运行:
./a.out & 

4.2 进程相关函数

4.2.1 创建进程——fork()函数

#include <unistd.h>
pid_t fork(void);
/*********************************************************************************  
* Description:  创建进程
* Input:        
* Return:        成功:	0:子进程
*						其他正整数:为子进程的ID
*				 失败: 	-1
* Others:        
* fork函数的特性:
* 	1. 执行fork函数之后,fork函数会返回两次;
* 	2. 在旧进程中返回时,返回值为0;
* 	3. 在新进程返回时,返回值为进程的 pid;
*   4. 也可以叫做,复制一个进程更加贴切。
* 使用fork函数总结:
* 	1. 在执行fork函数之前,操作系统只有一个进程,fork函数之前的代码只会被执行一次;
* 	2. 在执行fork函数之后,操作系统有两个几乎一样的进程,fork函数之后的代码会被执行两次。
**********************************************************************************/

示例:

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

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		while(1)
		{
			printf("子进程\n");
			sleep(1);
		}
	}else if(id < 0)
	{
		while(1)
		{
			printf("父进程获取到的id是子进程的ID:%d\n", id);
			sleep(1);
		}
	}else
		perror("fork");

 	return 0;
}

4.2.2 退出进程——exit()函数和_exit()函数

#include <stdlib.h>
void exit(int status);
/*********************************************************************************  
* Description:  在结束进程的时候,会清理I/O缓存区
* Input:         status:传入的参数是程序退出时的状态码
* 				 正常退出:EXIT_SUCCESS(0)
* 				 非正常退出:EXIT_FAILURE( -1 或 1 )
* Return:       
* Others:        
**********************************************************************************/

#include <stdlib.h>
void _exit(int status);
/*********************************************************************************  
* Description:  在结束进程的时候,不会清理I/O缓存区,直接就会退出进程
* Input:         status:传入的参数是程序退出时的状态码
* 				 正常退出:EXIT_SUCCESS(0)
* 				 非正常退出:EXIT_FAILURE( -1 或 1 )
* Return:        
* Others:        
**********************************************************************************/

4.2.3 回收进程——wait()函数和waitpid()函数

#include <unistd.h>
pid_t wait(int *status);
/*********************************************************************************  
* Description:  调用该函数会使进程阻塞,直到任一个子进程结束 或 该进							程接收到一个信号为止;
* 				 阻塞父进程,会一直等待僵尸进程的产生,产生后将其杀死
* Input:         status:保存状态的地址;
* 				 一般不需要关心子进程返回的状态,因此参数为NULL。
* Return:        成功:	返回进程的ID
*				 失败: 	0或-1
* Others:        常用用法:wait(NULL);
**********************************************************************************/

#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int options);
/*********************************************************************************  
* Description:  非阻塞父进程,直到有僵尸进程的产生,但是,只有父进程的程序执行到此函数的时候,才会执行这个函数,才会把僵尸进程给杀死
* Input:         pid:进程ID
* 					pid=-1 :等待任意子进程
*					pid=>0 :等待与pid相等的子进程
*					pid==0 :等待组ID等于调用进程组ID的任一进程。
*					pid<-1 :等待组ID等于pid绝对值的任一进程。
* 				 status:保存状态的地址;
* 				 options:0:阻塞, WNOHANG:非阻塞
* 				 一般不需要关心子进程返回的状态,因此参数为NULL。
* Return:        成功:	返回进程的ID
*				 失败: 	0或-1
* Others:        常用用法:waitpid(-1, NULL, WNOHANG);
**********************************************************************************/

4.2.4 回收僵尸进程1

产生僵尸进程

/******************************
    > File Name    : 15.c
    > Author       : GK
    > Description  : 产生僵尸进程
    > Created Time : 2020年11月01日 星期日 19时14分01秒
 *****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	int n=3;
	if(id == 0)
	{
		while(n--)
		{
			printf("子进程:%d\n", n);
			sleep(1);
		}
		printf("退出子进程\n");
		exit(0);  //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
	}else if(id > 0)
	{
		while(1)
		{
			printf("父进程获取到的id是子进程的ID:%d\n", id);
			sleep(1);
		}
	}else
		perror("fork");

 	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ ps -el | grep "15"
1 Z  1000   4366   4365  0  80   0 -     0 -      pts/0    00:00:00 15 <defunct>
gaokuo@Linux:~/Public/8练习$ 

回收进程的方式 wait(NULL)

/******************************
    > File Name    : 13.c
    > Author       : GK
    > Description  : 将以上函数进行综合(创建进程、退出进程、回收进程)
    			   :回收进程的方式 wait(NULL)
    > Created Time : 2020年11月01日 星期日 19时14分01秒
 *****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	int n = 3;
	if(id == 0)
	{
		while(n--)
		{
			printf("子进程:%d\n", n);
			sleep(1);
		}
		printf("退出子进程\n");
		exit(0);  //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
	}else if(id > 0)
	{
		//使用wait()函数回收进程时,会阻塞进程
		wait(NULL);
		printf("僵尸进程被回收\n");
		while(1)
		{
			//使用waitpid()函数回收进程时,只会当程序执行到此函数的时候才会回收子进程
			printf("父进程获取到的id是子进程的ID:%d\n", id);
			sleep(1);
		}
	}else
		perror("fork");

 	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 13
cc     13.c   -o 13
gaokuo@Linux:~/Public/8练习$ ./13
子进程:2
子进程:1
子进程:0
退出子进程
僵尸进程被回收
父进程获取到的id是子进程的ID:4295
父进程获取到的id是子进程的ID:4295
父进程获取到的id是子进程的ID:4295
父进程获取到的id是子进程的ID:4295

回收进程的方式 waitpid(-1, NULL, WNOHANG)

/******************************
    > File Name    : 14.c
    > Author       : GK
    > Description  : 将以上函数进行综合(创建进程、退出进程、回收进程)
    > Created Time : 2020年11月01日 星期日 19时14分01秒
 *****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	int n = 3;
	if(id == 0)
	{
		while(n--)
		{
			printf("子进程:%d\n", n);
			sleep(1);
		}
		printf("退出子进程\n");
		exit(0);  //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
	}else if(id > 0)
	{
		//使用wait()函数回收进程时,会阻塞进程
		//wait(NULL);
		while(1)
		{
			//使用waitpid()函数回收进程时,只会当程序执行到此函数的时候才会回收子进程
			waitpid(-1, NULL, WNOHANG);
			printf("父进程获取到的id是子进程的ID:%d\n", id);
			sleep(1);
		}
	}else
		perror("fork");

 	return 0;
}

终端执行结果:

gaokuo@Linux:~/Public/8练习$ make 14
cc     14.c   -o 14
gaokuo@Linux:~/Public/8练习$ ./14
父进程获取到的id是子进程的ID:4339
子进程:2
父进程获取到的id是子进程的ID:4339
子进程:1
父进程获取到的id是子进程的ID:4339
子进程:0
父进程获取到的id是子进程的ID:4339
退出子进程
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339

4.3 创建守护进程

4.3.1 守护进程的特点

  1. 在后台偷偷的运行,是Linux中的后台服务进程,守护进程常常在系统启动时启动,系统结束时结束。
  2. 在后台记录系统的日志
  3. 也就是通常所说的Daemon进程,是Linux中的后台服务进程,独立于终端。

4.3.2 守护进程的编写步骤

  1. 创建子进程,父进程退出
  2. 在子进程中创建新会话
  3. 改变当前目录为根目录
  4. 重设文件权限掩码
  5. 关闭文件描述符
  6. 创建完成后,就可以执行想要做的程序了

示例:

/******************************
    > File Name    : 16.c
    > Author       : GK
    > Description  : 守护进程
    > Created Time : 2020年11月01日 星期日 19时14分01秒
 *****************************/
#include <stdio.h>
#include <unistd.h>

int main()
{
	// 第一步
	pid_t pid = fork();

	if(pid > 0){
		exit(0); 
	}
	// 第二步
	setsid();
	// 第三步
	chdir("./"); //以根目录作为工作目录,需要sudo
	// 第四步
	umask(0);
	// 第五步
	for(int i=0; i < 1024; i++){
		close(i);
	}
	// 第六步
	//让守护进程 每2s 写一次文件
	FILE *fp = fopen("log.txt","w");
	while(1){    

		sleep(2);

		fputs("hello ",fp);

		fflush(fp);

	}

	fclose(fp);
 	return 0;
}

4.4 在进程中启动其他程序——exec函数族

4.4.1 功能:

可以启动二进制文件,也可以启动脚本文件

4.4.2 常用后缀

  1. l: 代表以列表形式传参
  2. v:代表以矢量数组形式传参
  3. p:代表使用环境变量path来寻找指定执行文件
  4. e:代表用户提供自定义的环境变量

4.4.3 函数族常用函数

4.4.3.1 execl()函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...)
/*********************************************************************************  
* Description:  需要填写程序的路径,才可以执行
* Input:         path:程序的路径或指令的路径
* 				 arg: 程序的名字或指令的名字
* 				 ...   执行程序的参数,没有则填NULL
* Return:        成功:	不返回
*				 失败: 	返回-1
* Others:        
**********************************************************************************/
4.4.3.2 execlp()函数
#include <unistd.h>
int execlp(const char *file, const char *arg, ...)
/*********************************************************************************  
* Description:  从PATH 环境变量中查找文件并执行
* 				 寻找的路径是PATH路径
* Input:         file:程序的名字或指令的名字
*  				 arg: 程序的名字或指令的名字
* 				 ...   执行程序的参数,没有则填NULL
* Return:        成功:	不返回
*				 失败: 	返回-1
* Others:        
**********************************************************************************/
4.4.3.3 execv()函数
#include <unistd.h>
int execv(const char *path, char *const argv[])
/*********************************************************************************  
* Description:  
* Input:         path:用户自定义的程序路径或指令路径
* 				 arg: 程序的名字或指令的名字的指针数组,并且需要以空指针(NULL)结束
* Return:        成功:	不返回
*				 失败: 	返回-1
* Others:        
**********************************************************************************/
4.4.3.4 execve()函数
#include <unistd.h>
int execve(const char *path, char *const argv[], char *const envp[])
/*********************************************************************************  
* Description:  用户自定义的文件路径去执行
* Input:         path:用户自定义的程序的路径或指令的路径
* 				 arg: 程序的名字或指令的名字的指针数组,并且需要以空指针(NULL)结束
* 				 envp  传递给执行文件的新环境变量数组
* Return:        成功:	不返回
*				 失败: 	返回-1
* Others:        
**********************************************************************************/

示例

/******************************
    > File Name    : 17.c
    > Author       : GK
    > Description  : 
    > Created Time : 2020年11月01日 星期日 19时14分01秒
 *****************************/
#include <stdio.h>
#include <unistd.h>

int main()
{
	pid_t result;
	result = fork();
	if(result > 0)
	{
		if(execlp("ls", "ls", "-l", NULL) < 0)
		{
			perror("execlp");
			exit(-1);
		}
	}
	return 0;
}

4.5 进程间通信

4.5.1 早期的进程通信

4.5.1.1 无名管道
(1) 简介

在Linux内核(内存)中创建一个管道。
管道内部的实质是:队列。

(2) 特点
  1. 只能用于亲缘关系的进程之间的通信
  2. 半双工的通信方式,固定的读和写
  3. 管道没有数据,读管道阻塞
  4. 如果写端关闭,读管道读0返回
  5. 如果读端关闭,写管道将会产生管道破裂,默认结束进程
  6. 遵循先进先出
  7. 不支持lseek
(3) 创建无名管道——pipe()函数
#include <unistd.h>
int pipe(int filedes[2]);
/*********************************************************************************  
* Description:  创建管道 (无名)
* Input:         filedes[0]为管道的读取端
*				 filedes[1]为管道的写入端
* Return:        成功:	返回0
*				 失败: 	返回-1
* Others:        只有具有亲缘关系的进程之间才能使用这种无名管道
**********************************************************************************/

示例

// 通过无名管道 实现子父进程间的通信 

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

int main(void)
{
    int fd[2];
    pipe(fd);   //创建无名管道
    
    int ret;
    int rfd = fd[0];   //fd[0] :读端
    int wfd = fd[1];   //fd[1] :写端

    pid_t pid;
    pid = fork();
    if(pid==0){
        close(rfd); //子进程 写管道,关闭无用的管道 rfd
        char buf[32];
        while(1){
            gets(buf);
            write(wfd,buf,sizeof(buf));
        }
		exit(0);
    }
    else if(pid>0){
        close(wfd);   //父进程 读管道 关闭无用的管道 wfd
        char buf[32]={0};
        while(1){
            ret = read(rfd,buf,sizeof(buf));
            printf("ret:%d,buf:%s\n",ret,buf);
        }
    }
    else{
        perror("fork");
    }
 
    return 0;
}
4.5.1.2 有名管道
(1) 简介
  1. 先创建一个管道文件,这个文件名在磁盘中,而数据是在内存中,每传输完成后,管道内的数据就会清零。
  2. 通过命令mkfifo创建,或通过函数mkfifo创建
  3. 有名管道相当于在读写文件。
(2) 特点
  1. 通过mkfifo创建有名管道
  2. 名字存在磁盘中,可以实现非亲缘进程通信
  3. 数据存在内存中
  4. 如果写端关闭,读管道读0返回
  5. 如果读端关闭,写管道将会产生管道破裂,默认结束进程
  6. 遵循先进先出
  7. 不支持lseek
(3) 创建有名管道——mkfifo()函数
#include <unistd.h>
int mkfifo(const char *pathname, mode_t mode);
/*********************************************************************************  
* Description:  创建管道 (有名)
* Input:         pathname:路径名,mode:权限
* Return:        成功:	返回0
*				 失败: 	返回-1
* Others:        非情缘关系进程也可以通信
**********************************************************************************/

示例

  1. 创建有名管道,并且在管道里写数据
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    //mkfifo : 创建有名管道
    int mkfifo_ret = mkfifo("myfifo",0666);
    if(mkfifo_ret == -1){
        perror("mkfifo");
        exit(-1);
    }

    printf("有名管道创建成功\n");
	//写有名管道f1 

    int fd = open("f1",O_WRONLY);
    if(fd==-1){
        perror("open");
        exit(-1);
    }

    char buf[256];
    int ret;
    while(1){
        printf("请输入数据\n");
        gets(buf);
        ret = write(fd,buf,sizeof(buf));
        if(ret <= 0){
            break;
        }
    }
    
    close(fd);
    
    return 0;
}
  1. 在有名管道中读出数据
#include<stdio.h>

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

int main(void)
{
    //读有名管道f1 

    int fd = open("f1",O_RDONLY);
    if(fd==-1){
        perror("open");
        exit(-1);
    }

    char buf[256];
    int ret;
    while(1){
        ret = read(fd,buf,sizeof(buf));
        if(ret <= 0){
            if(ret==0){
                printf("写端已关闭\n");
            }
            break;
        }
        printf("ret:%d,buf:%s\n",ret,buf);
    }
  
    close(fd);
    return 0;
}
4.5.1.3 信号
(1) 简介

信号,就相当于捕获一些信号的产生,然后做出相应的执行动作。
1. 硬件来源:比如按下了键盘或其他硬件故障;
2. 软件来源:最常用发送信号的系统函数kill、raise、alarm以及一些非法操作。
信号的处理方式:
1. 忽略信号 SIG_IGN
2. 捕获信号(自定义信号处理)
3. 默认 SIG_DFL
常见的信号:
2) SIGINT // ctrl+c
9) SIGKILL //结束 (不可捕获,忽略)
11) SIGSEGV //段错误
13) SIGPIPE //管道破裂
14)SIGALRM //闹钟信号
17) SIGCHLD //僵尸信号
18) SIGCONT //继续
19) SIGSTOP //停止 (不可捕获,忽略)
20) SIGTSTP //ctrl+z

(2) 信号的相关函数
1> 给进程发送信号——kill()函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
/*********************************************************************************  
* Description:  发信号
* Input:         pid:进程号,sig:信号编号
* 				 pid>0        将信号传给进程识别码为pid的进程
*				 pid=0        将信号传给和目前进程相同的进程组
* 				 pid=-1       将信号广播传送给系统的所有进程
* 				 pid<0        将信号传给进程组识别码为pid绝对值的所有进程
* Return:        成功返回0, 出错返回-1
* Others:        给进程发指定的信号
**********************************************************************************/
2> 给自己发送信号——raise()函数
#include <sys/types.h>
#include <signal.h>
int raise(int sig);
/*********************************************************************************  
* Description:  发信号
* Input:         sig:信号编号
* Return:        成功返回0, 出错返回-1
* Others:        给自己发指定的信号
**********************************************************************************/
3> 捕获信号并处理——signal()函数
#include <sys/types.h>
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
/*********************************************************************************  
* Description:  信号处理 
* Input:         signum:信号编号,handler:信号处理函数
* Return:        成功返回函数指针, 出错返回-1
* Others:        sighandler_t 是函数指针类型 
				 typedef void (*sighandler_t)(int);
**********************************************************************************/
(3) 示例
1> kiil函数
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
       

int main(void)
{
    int pid;
    int signum;
    int ret;
    while(1){
        printf("给进程发信号,请先输入pid,再输入信号值\n");
        scanf("%d",&pid);
        scanf("%d",&signum);

        kill(pid, signum);
    }

    return 0;
}
2> raise函数
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
       
int main(void)
{
    int signum;
    printf("给自己发信号,请输入信号值\n");
    scanf("%d",&signum);

    raise(signum);

    while(1);

    return 0;
}
3> signal函数
#include<stdio.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("接收到了 闹钟信号:%d\n",sig);
}

int main(void)
{
       
   // signal(SIGALRM,signal_handler); //捕获闹钟信号,处理方式:忽略

    signal(SIGINT,signal_handler);
    
    //注意信号的三种处理方式: 1. SIG_IGN 忽略
    //                         2. SIG_DFL 默认
    //                         3. 自定义函数 

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

    return 0;
}
(4) 回收僵尸进程2
/******************************
    > File Name    : 13.c
    > Author       : GK
    > Description  : 使用signal函数,回收僵尸进程
    			   :回收方式使用 waitpid() 方式。
    > Created Time : 2020年11月01日 星期日 19时14分01秒
 *****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

void signal_handler(int sig)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main()
{
	pid_t id = fork();
	int n = 3;
	if(id == 0)
	{
		while(n--)
		{
			printf("子进程:%d\n", n);
			sleep(1);
		}
		printf("退出子进程\n");
		exit(0);  //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
	}else if(id > 0)
	{
		//使用wait()函数回收进程时,会阻塞进程
		wait(NULL);
		printf("僵尸进程被回收\n");
		while(1)
		{
			//使用waitpid()函数回收进程时,只会当程序执行到此函数的时候才会回收子进程
			printf("父进程获取到的id是子进程的ID:%d\n", id);
			sleep(1);
		}
	}else
		perror("fork");

 	return 0;
}

4.5.2 system V 进程通信

system V 进程通信的相关命令

ipcs   //查看ipc对象
ipcrm -m shmid //删除共享内存  (根据id删除)
ipcrm -q msgid //删除消息队列  (根据id删除)
ipcrm -s semid //删除信号灯    (根据id删除)
4.5.2.1 共享内存:效率最高
(1) 打开或创建共享内存——shmget()函数
共享内存中的key值是IPC_PRIVATE :
	是私有的(用于亲缘关系的通信)  反复的执行进程程序,会导致反复的创建
使用ftok()函数的返回值:
	是共享的(用于非亲缘关系或亲缘关系的通信)反复的执行进程程序,不会反复的创建,shmid是惟一的了
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
/*********************************************************************************  
* Description:  创建共享内存
* Input:         key:key值 (IPC_PRIVATE 或 ftok的返回值) 
				 size:大小
				 shmflg:标志(同open函数的权限位)
* Return:        成功返回共享内存ID;不成功返回-1
* Others:        
**********************************************************************************/
(2) 建立地址映射——shmat()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
/*********************************************************************************  
* Description:  获取共享内存地址
* Input:         shmid:共享内存id 
				 shmaddr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
				 shmflg:标志(SHM_RDONLY:共享内存只读, 0:读写)
* Return:        成功返回地址。失败时返回-1
* Others:        
**********************************************************************************/
(3) 解除地址映射——shmdt()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);
/*********************************************************************************  
* Description:  解除映射
* Input:         
				 shmaddr:共享内存的地址
				
* Return:        成功返回返回 0 失败时返回-1
* Others:        
**********************************************************************************/
(4) 共享内存的控制——shmctl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*********************************************************************************  
* Description:  完成对共享内存的控制
* Input:         shmid:共享内存标识符
				 cmd:
					IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
					IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
					IPC_RMID:删除这片共享内存
				 buf:共享内存管理结构体。具体说明参见共享内存内核结构定义部分
* Return:        成功返回0, 出错返回-1
* Others:        给进程发指定的信号
**********************************************************************************/
4.5.2.2 消息队列:可以根据类型取消息
实质是链表
(1) 打开或创建消息队列——msgget()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int flag);
/*********************************************************************************  
* Description:  创建或打开消息队列
* Input:         key:key值 (IPC_PRIVATE 或 ftok的返回值) 
				 flag:标志(同open函数的权限位)
* Return:        成功返回队列ID,失败返回-1
* Others:        
**********************************************************************************/
(2) 发消息——msgsnd()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//消息包类型
struct msgbuf {
long mtype; 		/* 消息类型,必须 > 0 */
char mtext[1];      /* 消息文本 */
};
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
/*********************************************************************************  
* Description:	发送消息
* Input:		msqid:id
				ptr:消息包地址(发送)
				size:消息包正文大小,
				flag:方式(0:阻塞方式 IPC_NOWAIT:表示非阻塞方式)
* Return:       成功返回0,失败返回-1
* Others:        
**********************************************************************************/
(3) 收消息——msgrcv()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
/*********************************************************************************  
* Description: 读取消息
* Input:        msqid:id
				ptr:消息包地址(接收)
				size:消息正文大小
				type:类型(0:读取任意类型)
				flag:方式(0:阻塞方式 IPC_NOWAIT:表示非阻塞方式)
* Return:       成功返回消息数据的长度,失败返回-1
* Others:        
**********************************************************************************/
(4) 控制消息队列——msgctrl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/*********************************************************************************  
* Description:  控制消息队列
* Input:         msqid:消息队列的队列ID
				 cmd:
				 	IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf制定的地址中
				 	IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm域
				 	IPC_RMID:从系统内核中删除消息队列
				 buf:标志(同open函数的权限位)
* Return:        成功:0  ,失败:-1
* Others:        
**********************************************************************************/
4.5.2.3 信号灯集:用于实现同步或互斥
(1) 创建信号灯——semget()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
/*********************************************************************************  
* Description:  创建信号灯
* Input:		 key:key值 (IPC_PRIVATE 或 ftok的返回值) 
				 nsems:信号灯个数
				 semflg:标志(同open函数的权限位)
				
* Return:        成功返回0,失败返回-1
* Others:        
**********************************************************************************/
(2) 信号灯的操作——semop()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);
/*********************************************************************************  
* Description: 信号灯的操作
* Input:		semid:id
				sops:传入的结构体
				
				struct sembuf {
					short  sem_num;   //灯的编号
					short  sem_op;    
                                      // 1: +1操作  post
                                      //-1: -1操作  wait
                                      // 0: 等待,直到信号灯的值变成0	
									  
					short  sem_flg;   // 0:阻塞  IPC_NOWAIT:非阻塞 
				};

				nsops:灯的个数
* Return:       成功返回0,失败返回-1
* Others:        
**********************************************************************************/
(3) 信号灯的控制——semctl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);
/*********************************************************************************  
* Description:  控制信号灯
* Input:         semid:信号灯的编号
				 semnum:
				 cmd:对信号灯的操作
				 	IPC_STAT:获得该信号灯的semid_ds结构体中的第四个参数arg结构变量的buf域指向的semid_ds的结构中
				 	IPC_SETVAL:将信号量值设置为arg的val值
				 	IPC_GETVAL:返回信号量的当前值
				 	IPC_RMID:从系统值删除信号量(或信号量灯集)
* Return:        成功:不同的cmd的值返回不同的值
* 					IPC_STAT、 IPC_SETVAL、IPC_RMID返回0
* 					IPC_GETVAL返回信号量的当前值
* 				 失败:-1
* Others:        
**********************************************************************************/

4.5.3 BSD socket 网络通信

4.5.3.1 socket通信

5. 线程

5.1 线程相关概念

5.1.2 什么是线程?

线程是程序执行时的最小单位,即CPU调度和分派的基本单位,一个进程可以由多个线程组成,同一个进程中的多个线程之间共享此进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理

5.1.3 进程和线程的区别

5.1.3.1 相同点
  1. 两者均可并发执行
5.1.3.2 不同
  1. 地址空间:
    线程共享此进程中的地址空间,而进程之间是独立的地址空间。
  2. 资源拥有:
    线程共享此进程中的资源(如I/O、内存、CPU等),而进程之间的资源是相互独立的。
  3. 执行过程:
    线程不用独立运行,必须依赖存在于进程中,由进程提供多个线程进程执行,而每个独立的进程都有一个程序运行的入口、顺序执行序列、程序入口。
  4. 健壮性:
    其中一个线程崩溃,回使整个进程后死掉,而一个进程崩溃后,不会对其他进程产生影响。所以多进程要比多线程健壮。
  5. 进程或线程切换时:
    进程之间切换,消耗资源大,但是效率高;如果涉及到频繁的切换时,使用线程要比进程好!!!
    如果要求同时进行,又要共享某些变量的并发操作时,只能用线程。
5.1.3.3 优缺
  1. 线程执行开销小,但是不利于资源的管理和保护
  2. 进程执行开销小,但是能够很好的进行资源的管理和保护

5.2 线程相关函数

5.2.1 创建线程——pthread_create()函数

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
/*********************************************************************************  
* Description:  创建线程
* Input:         thread:线程id  
	  			 attr:线程属性 默认NULL
	   			 start_routine:线程函数
	  			 arg:函数运行的参数
* Return:        成功:	返回0
*				 失败: 	返回错误号
* Others:        不能用perror()进行打印,采用strerror();
**********************************************************************************/

使用pthread_create()函数来创建和使用线程的时候的注意事项:

  1. 当多线程同时访问同一个全局变量或全局数组时,一定要加互斥量来保护数据,也就是上锁,千万不要忘记解锁
  2. 正确处理线程结束的方式:一个线程的终止,线程的资源不会随线程的终止而释放,一定要添加pthread_detach()函数和pthread_join()函数,来释放该线程所占用的资源。
5.2.1.1 pthread_create函数的传参
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

char buf[32];  //同一进程中子线程主线程 共享全局变量
//线程函数
void * fun(void *argc)
{
	pthread_detach(pthread_self());
    while(1){    
        printf("子线程 buf:%s, argc = %d\n",buf, *((int *)&argc));
        sleep(2);
    }
    pthread_exit(NULL);
}

int main(void)
{
	int num = 6;
    pthread_t pid;
    pthread_create(&pid, NULL, fun, (void *)&num);

    while(1){
        printf("主线程写入buf\n");
        gets(buf);
    }
    return 0;
}

5.2.2 退出线程——pthread_exit()函数

#include <pthread.h>
void pthread_exit(void *retval);
/*********************************************************************************  
* Description:  线程退出
* Input:         retval:线程退出状态
* Return:        无
* Others:         
**********************************************************************************/

5.2.3 回收线程——pthread_detach()函数和pthread_join()函数

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
/*********************************************************************************  
* Description:  阻塞等待线程退出,回收线程的资源
* Input:         thread:线程id  
*	  			 retval:线程的状态
* Return:        成功:返回0
* 				 失败:返回错误号
* Others:         
**********************************************************************************/
#include <pthread.h>
int pthread_detach(pthread_t thread);
/*********************************************************************************  
* Description:  线程在运行结束后会自动释放系统分配的资源 
* Input:         thread:线程id
* Return:        成功:返回0
* 				 失败:返回错误号
* Others:         
**********************************************************************************/

5.2.4 取消线程——pthread_cancel()函数

#include <pthread.h>
int pthread_cancel(pthread_t thread);
/*********************************************************************************  
* Description:  取消线程
* Input:         thread:线程id
* Return:        成功:返回0
* 				 失败:返回错误号
* Others:         
**********************************************************************************/

5.2.5 过去本线程的ID——pthread_self()函数

#include <pthread.h>
pthread_t pthread_self(void);
/*********************************************************************************  
* Description:  获取本线程id
* Input:         无
* Return:        成功:返回本线程的ID
* Others:         
**********************************************************************************/

示例

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

pthread_t function1_id;
pthread_t function2_id;

void *pthread_function1_task(void *argc);
void *pthread_function2_task(void *argc);

char buf[32] = {0};

int main(void) 
{
    pthread_create(&function1_id, NULL, pthread_function1_task, NULL);
    pthread_create(&function2_id, NULL, pthread_function2_task, NULL);

	while(1);

    return 0;
}

/*
 * 线程功能:每隔5秒打印时间
 *
*/
void *pthread_function1_task(void *argc)
{
    pthread_detach(pthread_self());
    time_t seconds;

    while(1)
    {
        time(&seconds);
        printf("time: %s\n", ctime(&seconds));
        
        sleep(5);
    }
    pthread_exit(NULL);
}

/*
 * 线程功能:从终端获取字符串,然后输出到终端中
 *
*/
void *pthread_function2_task(void *argc)
{
    pthread_detach(pthread_self());
    while(1)
    {
        gets(buf);
        if( *buf == 'q')
        {
            exit(0);
        }else
        {
            printf("buf is : %s\n", buf);
        }

        sleep(1);
    }
    pthread_exit(NULL);
}

5.3 线程间通信

5.3.1 最简单的通信方式——全局变量

使用全局类型数据
如果定义了一个全局变量,可以使用
如果定义了一个全局类型的数据,不可以单独使用,一定要加锁。。。

如下面的例子:

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

int buf[128];

void * fun(void *argc)
{
    while(1)
    {    
        /*   共享数据     */
        memset(buf,0,sizeof(buf));
        int i;
        for(i=0;i<128;i++){
            buf[i] = i;
        }
    }
    pthread_exit(NULL);
}


int main(void)
{
    pthread_t pid;
    pthread_create(&pid, NULL, fun, NULL);
    while(1){
        /*   共享数据     */
        int i;
        getchar();
        for(i=0;i<128;i++){
            printf("%d,",buf[i]);
        }
        printf("\n");
    }
    return 0;
}

5.3.2 同步通信方式——信号量

强调要有先后顺序

5.3.2.1 初始化信号量——sem_init()函数
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value) 
/*********************************************************************************  
* Description:  信号量初始化
* Input:         sem:信号量
*				 pshared:指明信号量的类型
*						 共享区域(0:线程间,1:进程间)
*				 value:信号量初值
* Return:        成功:返回0
* 				 失败:返回-1
* Others:         
**********************************************************************************/
5.3.2.2 等待信号量(P操作)——sem_wait()函数
#include <semaphore.h>
int sem_wait(sem_t *sem); 
/*********************************************************************************  
* Description:  P操作(信号量减1)
* Input:         sem:信号量
* Return:        成功:返回0
* 				 失败:返回-1
* Others:        减到0值时阻塞
**********************************************************************************/
5.3.2.3 释放信号量(v操作)——sem_post()函数
#include <semaphore.h>
int sem_post(sem_t *sem);
/*********************************************************************************  
* Description:  V操作(信号量加1)
* Input:         sem:信号量
* Return:        成功:返回0
* 				 失败:返回-1
* Others:        不会阻塞
**********************************************************************************/

示例

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>

char buf[32];  //同一进程中子线程主线程 共享全局变量

sem_t sem1 ;    //定义信号量
sem_t sem2 ;    //定义信号量


//线程函数
void * fun(void *argc)
{

    while(1){    
        sem_wait(&sem1);         //sem1 阻塞
        sleep(4);
        printf("子线程buf:%s\n",buf);
        sem_post(&sem2);         //解开sem2 
    }
    pthread_exit(NULL);
}

int main(void)
{
    sem_init(&sem1, 0, 0);//信号量初始化
    sem_init(&sem2, 0, 1);//信号量初始化

    //创建线程 让子线程 执行fun
    pthread_t pid;
    pthread_create(&pid, NULL, fun, NULL);

    while(1){
        
        sem_wait(&sem2);        //sem2阻塞
        gets(buf);
        sem_post(&sem1);        //解开sem1

    }

    return 0;
}

5.3.3 互斥通信方式——互斥锁

强调保证共享数据的完整性,没有顺序

5.3.3.1 初始化互斥锁——pthread_mutex_init()函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *restrictattr);
/*********************************************************************************  
* Description:  初始化锁
* Input:         mutex:锁 
* 				 restrict attr:属性
* Return:        成功:返回0
* 				 失败:返回-1
* Others:         
**********************************************************************************/
5.3.3.2 上锁——pthread_mutex_lock()函数
#include <pthread.h>
int  pthread_mutex_lock(pthread_mutex_t *mutex)
/*********************************************************************************  
* Description:  P操作(信号量减1)
* Input:         mutex:锁
* Return:        成功:返回0
* 				 失败:返回非0
* Others:         
**********************************************************************************/
5.3.3.3 解锁——pthread_mutex_unlock()函数
#include <pthread.h>
int sem_post(sem_t *sem);
/*********************************************************************************  
* Description:  解锁
* Input:         mutex:锁
* Return:        成功:返回0
* 				 失败:返回非0
* Others:         
**********************************************************************************/

示例

//通过互斥锁  实现访问共享数据的完整性

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

int buf[128];

pthread_mutex_t mutex; //定义一把锁 

void * fun(void *argc)
{
    while(1){    
        
        pthread_mutex_lock(&mutex);   //加锁
        /*   共享数据     */
        memset(buf,0,sizeof(buf));
        int i;
        for(i=0;i<128;i++){
            buf[i] = i;
        }
        pthread_mutex_unlock(&mutex); //解锁
    }
    pthread_exit(NULL);
}


int main(void)
{
 
    pthread_mutex_init(&mutex,NULL); //互斥锁的初始化

    pthread_t pid;
    pthread_create(&pid, NULL, fun, NULL);

    
    while(1){
        
        pthread_mutex_lock(&mutex);   //加锁
        /*   共享数据     */
        int i;
        for(i=0;i<128;i++){
            printf("%d,",buf[i]);
        }
        printf("\n");
            
        pthread_mutex_unlock(&mutex); //解锁
    }

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值