嵌入式学习系列来到I/O和进程部分,本篇主要讲述了在Linux下,I/O和进线程之间的知识总结,分析了标准I/O和文件I/O区别,重点讲解文件描述符的含义和具体文件的I/O编程接口;解释了库的制作,包括动态库和静态库制作与使用,以及两者区别;讲解了进程和线程的区别和联系、如何创建多进程以及守护进程;对于Linux进程间通信,讲解几种常用的进程通信方法,包括管道通信、信号通信、共享内存、消息队列等;对于Linux多线程编程,讲解了多线程编程方法及注意事项等。
整篇是本人通过学习进行笔记记载,内容丰富,将所涉及知识集中在一篇中,方便学习,如有不足,还请指出。另外我们提供嵌入式项目毕设资料与服务,远程部署、代码讲解、全方位服务。我们有专门的毕设项目辅导群(QQ:596557438)!!!
项目源码资料下载地址:箫声商城
易学蔚来全套毕设演示(看上哪个选那个): 易学蔚来全套毕业设计汇总 · 语雀
毕设服务真实反馈,在线观看: 客户真实反馈 · 语雀
一、概念简介
1.文件
在Linux中,一切皆文件
2.文件类型
Linux的七种文件属性
(1)普通文件 -
(2)目录文件 d
(3)符号链接文件(软链接): l
(4)字符设备文件:c
(5)块设备文件 :b
(6)管道文件:p
(7)套接字文件:s
main函数解释:
二、IO
1.IO的分类
1.1 文件IO和标准IO
对一个文件有两种不同的操作方式
1) 文件IO------(linux系统提供) 系统调用
文件IO------(linux系统提供) 系统调用:当我们的应用程序要使用一些底层的功能的时候,不应该自行访问底层,而应该向操作系统发出请求。
特点:
不带缓冲区
操作系统直接提供的函数接口
调用系统调用是很耗费资源的
操作的文件是普通文件或者设备文件(硬件)
通过文件描述符(非负的数字)操作文件
2)标准IO------(C库提供)
C库函数:在系统调用接口之上封装的接口,一个C库函数可以封装多个系统调用函数。
作用:
增强了代码的可移植性,复用性
提高了效率。 标准IO增加了一个【缓冲机制】
操作文件一般为普通文件
通过文件流(指针)操作文件
2.标准IO
2.1 流
流:文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为“流”
为啥称结构体指针为流?
因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据(每一次的放入数据:均是需要通过文件指针来进行读写指向的文件),存在三个特点:
(1)有源头:APP
(2)有目的:缓冲区
(3)持续性:不断放入数据到缓冲区
2.2 流的分类
文件被打开的时候,会默认具备3个类
文本流:在流中处理的数据是以字符出现
二进制流:流中处理的是二进制序列,若流中有字符,则用一个字节的二进制ASCII码表示;若是数字,则用对应的二进制数表示,对'\n'不进行变换
系统默认打开了3个流指针
stdin (标准输入,终端键盘进行输入)
stdout(标准输出,终端打印出来)
stderr(标准错误,终端打印出来,不带缓冲区(意味着每一次出错就会立即刷新缓冲区))
2.3 缓冲机制
全缓冲:缓冲区被放满,程序结束,强制刷新--->>会引起缓冲区的刷新
行缓冲:缓冲区被放满,程序结束,强制刷新,遇到换行符--->>会引起缓冲区的刷新
不带(无)缓冲:不存在缓冲区的概念---<<每一次读写都是直接输出:stderr>>
全缓存:通过fopen函数打开的流指针,这个流指针fp的缓冲区大小是 4*1024 4Kbyte
//1.缓存区满
//2.fclose(fp)
//3.return
//4.exit
//5.fflush(fp)
printf、stdin 、stdout是行缓存,缓冲区大小是 1024byte == 1Kbyte
//1.行缓存满了 或遇到'\n'输出条件
//2.fflush可以强制刷新 (fflush)
//3.文件关闭的时候 fclose(stdout)
//4.程序结束的时候exit return
无缓存:stderr
#include <stdio.h>
int main(int argc, char *argv[])
{
// printf("hello world!\n");
printf("hello world!");
//缓存区的大小:1024Bytes
//printf("size=%d\n",stdout->_IO_buf_end - stdout->_IO_buf_base);
int i ;
for(i = 0;i < 1013;i++)
{
fputc(' ',stdout);
}
//fflush(stdout);//强制刷新标准输出流
//fclose(stdout);//关闭标准输出流
//return 0;
//exit(0);
while(1);
return 0;
}
每一个终端都是一个文件: pts/xxx 这个就是终端对应的文件,这个文件的名字是以数字命名的
这个文件存储在 : /dev/pts/xxx 这些文件是由linux系统自动创建。
当打开一个终端时,就会重建一个新的文件与之对应 stdin、stdout、stderr都指向的是同一个文件(终端文件)。
示例:直接向终端写入'x'
ps :查看当前终端运行的进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("/dev/pts/3", "w");
if(NULL == fp)
{
perror("fopen");
return -1;
}
int n = 10;
while(1)
{
fputc('x', fp);
//sleep(1);
}
fclose(fp);
return 0;
}
3.操作文件
[[fopen/fclsoe/fgetc/fputc/fgets/fputs]=文本流/[fread/fwrite]=二进制流]
3.1 fopen(打开文件)
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:打开文件
参数:
path: 文件路径或者打开文件的名字(包含指针类型)
mode:
r : 以只读方式打开,文件必须存在
r+ : 以读写方式打开,文件必须存在
w : 以写的方式打开,文件不存在,则创建,文件存在,则清空
w+ : 以读写的方式打开,文件不存在,则创建,文件存在,则清空
a : 以追加的方式打开(可写),文件不存在,则创建。
文件存在,则追加在文件的末尾
a+ : 以追加的方式打开(读写),文件不存在,则创建。
文件存在,则追加在文件的末尾
返回值:
成功: FILE *fp 流指针
失败: NULL
FILE *fp = fopen("./1.txt", "w+");
FILE:系统会自动为使用的文件在内存中开辟一片空间,来存储该文件的详细信息,这个空间类型为 FILE 结构体类型,该结构体由系统设计。
FILE *:流指针,在标准IO中,每次成功打开一个文件,都会返回一个流指针,这个流指针就描述了一个文件,所有的标准IO都围绕流指针来进行。
//没写一个函数都会进行出错处理
strerror(errno); //参数就是错误码errno
perror("fopen"); //参数就是字符串
1)示例代码:利用标椎IO函数测试当前系统最大能打开的文件个数
同一个文件,可以存在多个流指针,与之对应。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
int count = 0;
FILE *fp;
while(1)
{
fp = fopen("./1.txt", "w+");
if(NULL == fp)
{
//printf("errno: %s\n", strerror(errno));
perror("fopen");
//return -1;
break;
}
printf("%p\n", fp);
count++;
}
printf("count=%d\n", count);
#if 0
printf("%d\n", argc);
printf("%s\n", argv[0]);
printf("%s\n", argv[1]);
printf("%s\n", argv[2]);
#endif
return 0;
}
2)输出结果![](https://i-blog.csdnimg.cn/blog_migrate/70df84e956c7f52e66bc31e7fe6ff39e.png)
1021+3 = 1024
stdin :标椎输入流
stdout:标准输出流
stderr:标准错误流
3.2 fclose(关闭文件)
int fclose(FILE *fp);
功能:关闭文件
参数:
fp:流指针
fclose(fp);
为什么要关闭一个文件
一、防止其他进程操作这个文件
二、释放结构体占用的资源
在程序结束时,系统自动回收资源(不完全),所以尽量写上fclose。
3.3 freopen(改变流指针指向)
FILE *freopen(const char *path, const char *mode, FILE *stream);
功能:改变流指针的指向
参数:
path: 文件路径名
mode: 打开方式
stream:流指针
1)示例代码
#include <stdio.h>
int main(int argc, char *argv[])
{
freopen("./2.txt","w",stdout);
/*原始*/
printf("xxxxxxxxxxxx\n");
printf("hello world!\n");
printf("xxxxxxxxxxxx\n");
/*修改后*/
printf("------------\n");
printf("hello world!\n");
printf("xxxxxxxxxxxx\n");
printf("------------\n");
printf("hello world!\n");
printf("xxxxxxxxxxxx\n");
return 0;
}
2)输出结果
3.4 fgetc/fputc(按字符(字节)输入/输出)
1. fgetc(获取字符)
int fgetc(FILE *stream);
功能:从指定的文件流中获取一个字符
参数:指定获取一个字符所处文件的文件流
返回值:成功返回获取到的字符值,读取到文件末尾返回EOF(-1),操作中失败返回负数
)
2. fputc(输出字符)
int fputc(int c, FILE*stream);
功能:向指定的文件流中输出一个字符
参数:
参数1:需要输出的指定字符(字符被称为单字节的整形)
参数2:指定输出字符到的文件对应的文件流
返回值:
成功返回刚写入的字符值
失败返回EOF(-1
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./2.txt", "r+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
printf("%d\n", fgetc(fp));
printf("%c\n", fgetc(fp));
printf("%c\n", fgetc(fp));
printf("%c\n", fgetc(fp));
printf("%c\n", fgetc(fp));
fputc(121, fp);
fputc(121, fp);
fputc(121, fp);
fputc(121, fp);
fputc(121, fp);
fputc(121, fp);
fclose(fp);
return 0;
}
2)输出结果
3.5 fgets/fputs(按行输入/输出)
1. fgets(读取一行数据)
char *fgets(char *s, int size, FILE *stream);
功能:读取一行的数据
参数:
s: 内存地址 char s[];
size: 读取的字节数(存储内容空间的大小--可以sizeof()测得)
stream: 流指针
返回值:
成功:读取到字符串的首地址(存储内容空间的首地址)
失败:NULL
1)示例代码:文件有多少行?
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen("./1.txt","r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
char s[2000] = {0};
int count =0;
while(1)
{
char *p = fgets(s,sizeof(s),fp);
if(NULL == p)
{
perror("fgets");
break;
}
printf("%s\n",p);
count++;
memset(s,0, sizeof(s));
}
printf("count = %d\n",count);
fclose(fp);
return 0;
}
2)输出结果
2. fputs(写入一行数据)
int fputs(const char *s, FILE *stream);
功能:写入一行的数据
参数:
s: 内存地址 char s[];
stream: 流指针
返回值:
成功:非零值
失败:-1
1)示例代码 :写入"hello world"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./2.txt", "a+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
char buf[100] = "hello world\n";
int n = fputs(buf, fp);
printf("n = %d\n", n);
fclose(fp);
return 0;
}
2)输出结果
3)示例代码: 从标椎输入(stdin)得到字符串 写入到 文件当中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./2.txt", "a+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
char buf[100] = {0};
while(1)
{
char *p = fgets(buf, sizeof(buf), stdin);
if(NULL == p)
{
perror("fgets");
break;
}
if( strncmp(buf, "quit", 4) == 0)
{
break;
}
int n = fputs(buf, fp);
printf("n = %d\n", n);
memset(buf, 0, sizeof(buf));
}
fclose(fp);
return 0;
}
3.6 fread(以指定大小读取文件)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:读取数据
参数:
参数1:存储读取一块内容之后的空间地址
参数2:块的大小---》建议给1
参数3:块的个数---》建议给sizeof()
参数4:要读取的文件对应的文件流
返回值:成功代表读取的块的个数,失败返回0
1)示例代码
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen("./1.txt","rb");
if(NULL == fp)
{
perror("fopen");
return -1;
}
char buf[100] = {0};
int n = fread(buf,4,25,fp);
printf("n=%d\n",n);
printf("buf:%s\n",buf);
fclose(fp);
return 0;
}
2)输出结果
3.7 fwrite(以指定大小写入文件)
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:写入数据
参数:
参数1:需要写入的内容的空间地址
参数2:块的大小---》建议给1
参数3:块的个数---》建议给sizeof()
参数4:要写入的文件对应的文件流
返回值:成功代表写入的块的个数,失败返回0
fwrite()函数写入文件的内容是二进制的。
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct A{
int m;
float n;
char ch;
};
int main(int argc, const char *argv[])
{
struct A a = {10, 3.14, 'a'};
printf("sizeof(a) = %d\n", sizeof(a));
FILE *fp = fopen("./2.txt", "wb+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
fwrite(&a, sizeof(struct A) , 1, fp);
rewind(fp);
struct A b;
fread(&b, sizeof(struct A), 1, fp);
printf("%d %f %c\n", b.m, b.n, b.ch );
fclose(fp);
return 0;
}
2)输出结果
3.8 文件流的定位(文件指针的指示位置)
1、perror(“string”);---》可以输出出错的原因
2、feof(FILE* Stream);
作用:判断文件是否抵达末尾(不管是文本文件还是二进制文件,都可以判断)
返回值:
抵达文件末尾---》返回值为非零
未抵达文件末尾---》返回值0
3.9 练习
1.复制文件
1)示例代码(copy)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
if(argc!=3)
{
printf("%s src_file dest_file\n", argv[0]);
return -1;
}
FILE *fp1 = fopen( argv[1] , "r");
if(fp1 == NULL)
{
perror("fopen");
return -1;
}
FILE *fp2 = fopen( argv[2] , "w");
if(fp2 == NULL)
{
perror("fopen");
return -1;
}
char buf[1024] = {0};
while(1)
{
char *p = fgets(buf, sizeof(buf), fp1);
if(p == NULL)
{
break;
}
fputs(buf, fp2);
memset(buf, 0 ,sizeof(buf));
}
printf("copy success!\n");
fclose(fp1);
fclose(fp2);
return 0;
}
1)输出结果
2.怪兽乐园
typedef struct Monster{
int ID;
char name[50];
char species[50];
char type[50];
}MOS;
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Monster{
int ID;
char name[32];
char species[32];
char type[32];
}MOS;
int createMonster(int n)
{
MOS m1[n], m2[n];
int i;
for(i=0; i<n; i++)
{
printf("请输入第%d号怪兽信息: ", i+1);
scanf("%d%s%s%s", &m1[i].ID, m1[i].name, m1[i].species, m1[i].type);
}
FILE *fp = fopen("Monster.txt", "wb+");
if(fp == NULL)
{
perror("fopen");
return -1;
}
fwrite(m1, sizeof(MOS) , n, fp);
rewind(fp);
fread(m2, sizeof(MOS), n, fp);
for(i=0; i<n; i++)
{
printf("%d %s %s %s\n", m2[i].ID, m2[i].name, m2[i].species, m2[i].type);
}
fclose(fp);
}
int main(int argc, const char *argv[])
{
createMonster(3);
return 0;
}
2)输出结果
3.10 fseek(定位文件指针)
int fseek(FILE *stream, long offset, int whence);
功能:定位文件指针
参数:
stream: 流指针
offset: 偏移量
100:向后偏移100个字节
-100:向前偏移100个字节
whence: 基点
SEEK_SET: 文件开头
SEEK_END: 文件末尾
SEEK_CUR: 文件当前位置
返回值:
成功:0
失败:-1
定位到文件末尾: fseek(fp, 0 , SEEK_END);
定位到文件末尾的前一个字节: fseek( fp, -1, SEEK_END );
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./test.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
fputc('a', fp);
fseek(fp, 0, SEEK_END);
fseek(fp, 100, SEEK_END);
fputc('b', fp);
fclose(fp);
return 0;
}
2)输出结果
3.11 ftell(获取文件大小)
long ftell(FILE *stream);
参数:
stream:要定位的文件流
返回值:
成功:当前文件指针的位置
失败:eof
3.12 rewind(文件指针返回到文件开头)
void rewind(FILE *stream);
功能:文件指针返回到文件开头
rewind()等价于(void)fseek(stream,OL,SEEK_SET)
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./1.txt", "r");
if(fp == NULL)
{
perror("fopen");
return -1;
}
fseek(fp, 0, SEEK_END);
int n = ftell(fp); //返回值:当前文件指针的位置
printf("1.txt的总大小 =%d 字节\n", n);
rewind(fp);//文件指针移到开头
n = ftell(fp);
printf("1.txt的总大小 =%d 字节\n", n);
fclose(fp);
return 0;
}
2)输出结果
3.13 实战练习:查单词
流程:
1.打开文件
循环
输入一个单词
遍历文件
打印出单词信息
重新开始查询
关闭文件
1)示例代码
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp =fopen("./dict.txt","r");
if(NULL == fp)
{
perror("fopen failed!\n");
return -1;
}
char let[32] = {0};
char str[100] = {0};
while(1)
{
printf("please input letter:");
fgets(let,sizeof(let),stdin);
if(let[0] == '\n' )
{
break;
}
int n = strlen(let);
rewind(fp);
while(1)
{
char *p = fgets(str,sizeof(str),fp);
if(NULL == p)
{
printf("fgets failed!\n");
break;
}
if(strncmp(let,str,n-1)==0)
{
printf("%s\n",str);
break;
}
memset(str,0,sizeof(str));
}
memset(let,0,sizeof(let));
}
fclose(fp);
return 0;
}
2)输出结果
3.14 格式化输出(fprintf、sprintf)
1. fprintf(将格式化数据打印到流中)
int fprintf(FILE *stream, const char *format, ...);
功能:将格式化数据打印到流中
参数:
stream: 流指针
format: 格式控制符
...:不定参数
fprintf( fp, "%d-%d-%d" , 2022,7,21);
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./a.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
fprintf(fp, "%d-%d-%d %d:%d:%d\n", 2022,7,21,11,23,30);
fclose(fp);
return 0;
}
2)输出结果
2.sprintf(打印到字符串中)
int sprintf(char *str, const char *format, ...);
功能:
参数:
str: 内存地址
format: 格式控制符
...:不定参数
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buf[1024] = {0};
sprintf(buf, "%d-%d-%d %d:%d:%d %f %s%c", 2022,7,21,
11,28,30, 3.14, "hello", 'a');
printf("%s\n", buf);
return 0;
}
2)输出结果
3.15 格式化输入(sscanf、fscanf)
1.sscanf(从内存中提取数据出来)
int sscanf(const char *str, const char *format, ...);
功能:从内存中提取数据出来
参数:
str: 内存地址
format: 格式控制符
...:不定参数
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buf[100] = "2022-7-21 11:40:30 hello";
int a,b,c,d,e,f;
char str[32] = {0};
sscanf(buf, "%d-%d-%d %d:%d:%d %s", &a,&b,&c,&d,&e,&f,str);
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
printf("%d\n", d);
printf("%d\n", e);
printf("%d\n", f);
printf("%s\n", str);
return 0;
}
2)输出结果
2.fscanf(从文件(流指针)中提取数据出来)
int fscanf(FILE *stream, const char *format, ...);
功能:从文件(流指针)中提取数据出来
参数:
stream: 流指针
format: 格式控制符
...:不定参数
1)示例代码
//(注意为了清晰无误地表示出不同的空白符,用 <\b>代表空格,<\t>表示制表符,<\n>表示换行符。)
#include <stdio.h>
#include <errno.h>
void fscanfTest(FILE* fp) {
char c1, c2, s1[100], s2[100];
int d;
// 第一部分:fscanf对空格的处理
printf("the content of file is:\n");
printf("hello<\\b>world<\\b><\\b>666lucky<\\n>");
printf("\n\n");
// %s不会跳过后面的空格
fscanf(fp, "%s", s1);
printf("%s!\n", s1); // hello!
// %s会跳过前面的一个空格
rewind(fp); // 将光标移回文件开头
fscanf(fp, "%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // hello! world!
// %*s会从文件流中读入,但是不会将值赋予变量(*的作用)
rewind(fp);
fscanf(fp, "%*s%s", s1);
printf("%s!\n", s1); // world!
// %s会跳过前面的多个空格
rewind(fp);
fscanf(fp, "%*s%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // world! 666lucky!
// %c不会跳过空格
rewind(fp);
fscanf(fp, "%*s%c", &c1);
printf("%c!\n", c1); // " !"
// format str中的一个空格表示如果文件流接下来有连续空格,都跳过
rewind(fp);
fscanf(fp, "%*s%*s %c", &c1);
printf("%c!\n", c1); // "6!"
rewind(fp);
fscanf(fp, "%*s%*s%*d%c", &c1);
printf("%c!\n", c1); // "l!"
rewind(fp);
fscanf(fp, "%*s%*s%*d %c", &c2); // 注意这里format str中的空格没起作用,是因为666和lucky之间没有空白符
printf("%c!\n", c2); // "l!"
rewind(fp);
fscanf(fp, "%*s%*s%*d%s", s1);
printf("%s!\n", s1); // "lucky!"
rewind(fp);
fscanf(fp, "%*s%*s%*d %s", s2);
printf("%s!\n", s2); // "lucky!"
// format str中的多个连续空格和一个空格的效果是一样的
rewind(fp);
fscanf(fp, "%*s %c", &c1);
printf("%c!\n", c1); // "w!"
rewind(fp);
fscanf(fp, "%*s %c", &c2);
printf("%c!\n", c2); // "w!"
// 第二部分:fscanf对制表符的处理
printf("the content of file is:\n");
printf("hello<\\t>world<\\t><\\t>666lucky<\\n>");
printf("\n\n");
// %s不会跳过后面的制表符
fscanf(fp, "%s", s1);
printf("%s!\n", s1); // hello!
// %s会跳过前面的一个制表符
rewind(fp);
fscanf(fp, "%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // hello! world!
// %s会跳过前面的多个制表符
rewind(fp);
fscanf(fp, "%*s%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // world! 666lucky!
// %c不会跳过制表符
rewind(fp);
fscanf(fp, "%*s%c", &c1);
printf("%c!\n", c1); // "<\\t>!"
// format str中的一个制表符表示如果文件流接下来有连续制表符,都跳过
rewind(fp);
fscanf(fp, "%*s%*s\t%c", &c1);
printf("%c!\n", c1); // "6!"
rewind(fp);
fscanf(fp, "%*s%*s%*d%c", &c1);
printf("%c!\n", c1); // "l!"
rewind(fp);
fscanf(fp, "%*s%*s%*d\t%c", &c2);
printf("%c!\n", c2); // "l!"
rewind(fp);
fscanf(fp, "%*s%*s%*d%s", s1);
printf("%s!\n", s1); // "lucky!"
rewind(fp);
fscanf(fp, "%*s%*s%*d\t%s", s2);
printf("%s!\n", s2); // "lucky!"
// format str中的多个连续制表符和一个制表符的效果是一样的
rewind(fp);
fscanf(fp, "%*s\t%c", &c1);
printf("%c!\n", c1); // "w!"
rewind(fp);
fscanf(fp, "%*s\t\t%c", &c2);
printf("%c!\n", c2); // "w!"
// 第三部分:fscanf对换行符的处理
printf("the content of file is:\n");
printf("hello<\\n>world<\\n><\\n>666lucky<\\n>");
printf("\n\n");
// %s不会跳过后面的换行符
fscanf(fp, "%s", s1);
printf("%s!\n", s1); // hello!
// %s会跳过前面的一个换行符
rewind(fp);
fscanf(fp, "%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // hello! world!
// %s会跳过前面的多个换行符
rewind(fp);
fscanf(fp, "%*s%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // world! 666lucky!
// %c不会跳过换行符
rewind(fp);
fscanf(fp, "%*s%c", &c1);
printf("%c!\n", c1); // "<\\n>!"
// format str中的一个换行符表示如果文件流接下来有连续换行符,都跳过
rewind(fp);
fscanf(fp, "%*s%*s\n%c", &c1);
printf("%c!\n", c1); // "6!"
rewind(fp);
fscanf(fp, "%*s%*s%*d%c", &c1);
printf("%c!\n", c1); // "l!"
rewind(fp);
fscanf(fp, "%*s%*s%*d\n%c", &c2);
printf("%c!\n", c2); // "l!"
rewind(fp);
fscanf(fp, "%*s%*s%*d%s", s1);
printf("%s!\n", s1); // "lucky!"
rewind(fp);
fscanf(fp, "%*s%*s%*d\n%s", s2);
printf("%s!\n", s2); // "lucky!"
// format str中的多个连续换行符和一个换行符的效果是一样的
rewind(fp);
fscanf(fp, "%*s\n%c", &c1);
printf("%c!\n", c1); // "w!"
rewind(fp);
fscanf(fp, "%*s\n\n%c", &c2);
printf("%c!\n", c2); // "w!"
// 第四部分:当空格、制表符以及换行符混杂时fscanf的处理
printf("the content of file is:\n");
printf("hello<\\b><\\t><\\n>world<\\t><\\b><\\n>666lucky<\\n>");
printf("\n\n");
// %s会跳过连在一起的空格、制表符和换行符
fscanf(fp, "%s%s", s2, s1);
printf("%s! %s!\n", s2, s1); // hello! world!
// 当作为空白符时,format str中的空格、制表符以及换行符是一样的,可以相互替代!
rewind(fp);
fscanf(fp, "%*s %c", &c1);
printf("%c!\n", c1); // "w!"
rewind(fp);
fscanf(fp, "%*s\t%c", &c2);
printf("%c!\n", c2); // "w!"
rewind(fp);
fscanf(fp, "%*s\n%c", &c1);
printf("%c!\n", c1); // "w!"
// 第五部分:[]符号在format str中的应用
printf("the content of file is:\n");
printf("hello<\\b><\\t><\\n>world<\\b><\\t>666lucky<\\n>");
printf("\n\n");
// [el]表示只读取'e'或者'l'这个字符,[0-9]表示只读取0-9这10个数字字符
// %[]之后的域都不起作用了,不会读取文件流。
// test#1: %c%[]s可以正常工作
// output#1: h! ell!
errno = 0;
d = fscanf(fp, "%c%[el]s", &c1, s1);
if (d == 2) printf("%c! %s!\n", c1, s1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#2: %[]s后面的%c没有正常读取
// output#2: d = 2
errno = 0;
rewind(fp);
d = fscanf(fp, "%c%[el]s%c", &c2, s2, &c1);
if (d == 3) printf("%c! %s! %c!\n", c2, s2, c1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#3: %[]s后面的%s没有正常读取
// output#3: d = 2
errno = 0;
rewind(fp);
d = fscanf(fp, "%c%[el]s%s", &c1, s1, s2);
if (d == 3) printf("%c! %s! %s!\n", c1, s1, s2);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#4: 再次运行fscanf函数就可以继续读取文件流
// output#4: o! world!
errno = 0;
d = fscanf(fp, "%c%s", &c2, s2);
if (d == 2) printf("%c! %s!\n", c2, s2);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// [^el]表示不读取'e'也不读取'l'这个字符,[^0-9]表示不读取0-9的数字字符
// %[^]之后的域都不起作用了,不会读取文件流。
// test#5: %c%[^]s可以正常工作,注意下面的%[^w]s这个域读取了空格、制表符以及换行符。
// output#5: h! ello<\\b><\\t><\\n>!
errno = 0;
rewind(fp);
d = fscanf(fp, "%c%[^w]s", &c1, s1);
if (d == 2) printf("%c! %s!\n", c1, s1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#6: %[^]s后面的%s没有正常读取
// output#6: d = 2
errno = 0;
rewind(fp);
d = fscanf(fp, "%c%[^w]s%s", &c2, s2, s1);
if (d == 3) printf("%c! %s! %s!\n", c2, s2, s1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#7: 再次运行fscanf函数就可以继续读取文件流
// output#7: w! orld!
errno = 0;
d = fscanf(fp, "%c%s", &c1, s1);
if (d == 2) printf("%c! %s!\n", c1, s1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#8: %[^\n]s可以一直读取到行末尾,哪怕遇到空格或者制表符。
// output#8: h! ello<\\b><\\t>!
errno = 0;
rewind(fp);
d = fscanf(fp, "%c%[^\n]s", &c2, s2);
if (d == 2) printf("%c! %s!\n", c2, s2);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#9: %[^ ]s不会读取空格,但是会读取制表符和换行符
// output#9: <\\t><\\n>world!
errno = 0;
rewind(fp);
d = fscanf(fp, "%*s%*c%[^ ]s", s1);
if (d == 1) printf("%s!\n", s1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#10: %[^\t]s不会读取制表符,但是会读取空格和换行符
// output#10: <\\n>world<\\b>!
errno = 0;
rewind(fp);
d = fscanf(fp, "%*s%*c%*c%[^\t]s", s2);
if (d == 1) printf("%s!\n", s2);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#11: %[^]s不会跳过前面的空白符
// output#11: <\\b><\\t><\\n>wo!
errno = 0;
rewind(fp);
d = fscanf(fp, "%*s%[^r]s", s1);
if (d == 1) printf("%s!\n", s1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// 第六部分:出错的情况
// 从第五部分 test#2 以及 test#3 的例子中可以看出,fscanf的返回值表示能够正确赋值的域的个数。如果出错,fscanf返回EOF。
// 怎样才算出错?如果还没有任何一个域匹配成功或者任何一个匹配失败发生之前,就达到了文件流末尾,就算出错;或者读取文件流出错。就这两种情况。
// 即使所有域都不匹配,但只要没到达文件流末尾并且读取文件流过程中没有发生错误,就不算出错,errno就是0。此时,fscanf返回0。
printf("the content of file is:\n");
printf("hello");
printf("\n\n");
// test#1: 此时的%c发生匹配失败,所以返回值为0。
// output#1: d = 0
errno = 0;
d = fscanf(fp, "%*s%c", &c1);
if (d == 1) printf("%c!\n", c1);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
// test#2: 继续读取,已经到达文件流末尾,返回EOF。
// output#2: d = -1
errno = 0;
d = fscanf(fp, "%c", &c2);
if (d == 1) printf("%c!\n", c2);
else {
printf("d = %d\n", d);
if (errno != 0) perror("fscanf");
else printf("Error: no matching characters!\n");
}
}
int main(int argc, char* argv[]) {
FILE *fp;
if (argc < 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}
if ((fp = fopen(argv[1], "r")) == NULL) {
printf("Error: cannot open file\n");
return 1;
}
fscanfTest(fp);
fclose(fp);
return 0;
}
2)输出结果
3.16 时间函数(time、locatime)
1.time(从1970-1-1 0:0:0开始到现在的秒数)
#include <time.h>
#include <unistd.h>
time_t t;
time_t time(time_t *t);
功能:从1970-1-1 0:0:0开始到现在的秒数
参数:
t: &t / NULL
返回值:
成功:从1970-1-1 0:0:0开始到现在的秒数
失败:-1
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
time_t t;
while(1)
{
int m = time(&t);
printf("秒数:%d\n", m);
sleep(1);
}
return 0;
}
2)输出结果
2.localtime(从1970-1-1 0:0:0开始到现在的秒数转化成日历)
#include <time.h>
time_t t;
struct tm *localtime(const time_t *timep);
功能:从1970-1-1 0:0:0开始到现在的秒数转化成日历
参数:
timep: &t
返回值:
成功:struct tm *
失败:NULL
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
};
1)示例代码
/***********************************************/
练习:获取系统时间,并将系统时间 打印并输出到 文件中
[2024-04-07 14:07:30]
[2024-04-07 14:07:31]
[2024-04-07 14:07:32]
/************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(int argc, const char *argv[])
{
FILE *fp = fopen("./time.txt", "a");
if(fp == NULL)
{
perror("fopen");
return -1;
}
while(1)
{
time_t t;
time(&t);
struct tm *p = localtime(&t);
printf("%d-%d-%d %d:%d:%d\n", p->tm_year+1900,
p->tm_mon+1,p->tm_mday,
p->tm_hour,p->tm_min,p->tm_sec
);
fprintf(fp, "%d-%d-%d %d:%d:%d\n", p->tm_year+1900,
p->tm_mon+1,p->tm_mday,
p->tm_hour,p->tm_min,p->tm_sec
);
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}
2)输出结果
4.文件I/O
4.1 文件描述符
文件描述符实际上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,进程使用它来标识打开的文件。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
Linux系统为程序中每个打开的文件都分配一个文件描述符,文件IO操作通过文件描述符来完成。
文件描述符在形式上是一个顺序分配的非负整数。从0开始分配,依次递增。比如 0,1,2表示 stdin stdout stderr,一般最大打开的文件描述符数量为1024(0~1023)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
printf("%d\n", stdin->_fileno);
printf("%d\n", stdout->_fileno);
printf("%d\n", stderr->_fileno);
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
4.2 fdopen(文件IO转化标准IO)
FILE *fdopen(int fd, const char *mode);
功能:把文件IO转化成标准IO
参数;
fd: 文件描述符
mode: 打开方式
返回指:
成功:FILE *
失败:NULL
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp = fdopen(1, "w");
fprintf(fp, "%d-%d-%d\n", 2022,7,21);
return 0;
}
2)输出结果
4.3 常用的文件I/O函数
/open/close/read/write/lseek
4.4 open(打开文件)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//打开文件,文件已存在
int open(const char *pathname, int flags);
功能:打开指定的文件
参数:
参数1:所需打开文件的名字(包含路径)
参数2:打开文件的方式---》主标志 | 副标志
返回值:成功代表一个大于0 的数字(文件描述符),失败返回-1(errno ie set...)
//创建文件,文件不存在
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname: 文件的路径名
flags:
O_RDONLY:只读 ①
O_WRONLY:可写 ②
O_RDWR: 读写 ③
O_CREAT :创建 ④
O_TRUNC :清空 ⑤
O_APPEND:追加 ⑥
O_EXCL:如果O_CREAT时文件存在,可返回错误消息。
实际创建的文件权限需要经过一个公式计算得到: mode & (~umask)
mode: 权限
返回指:
成功:文件描述符fd
失败:-1
标准IO 文件IO
r O_RDONLY
r+ O_RDWR
w O_WRONLY | O_CREAT | O_TRUNC
w+ O_RDWR | O_CREAT | O_TRUNC
a O_WRONLY | O_CREAT | O_APPEND
a+ O_RDWR | O_CREAT | O_APPEND
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = open("./6.txt", O_CREAT|O_RDWR, 0666);
if(fd < 0)
{
perror("open");
return -1;
}
printf("fd=%d\n", fd);
printf("open success!\n");
close(fd);
return 0;
}
2)输出结果
4.5 close(关闭文件)
#include <unistd.h>
int close(int fd);
4.6 read(读取数据)
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
参数:
fd: 文件描述符:open函数的返回值
buf: 内存地址 char buf[];
count: 读取的字节数----->>sizeof测得
返回值:
成功:返回实际读取到的字节数n
n == 0 取到文件末尾
n > 0 成功读取到的字节数
失败:n<0 读取失败
1)示例代码(统计文件的大小)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = open("./1.txt", O_RDWR);
if(fd < 0)
{
perror("open");
return -1;
}
printf("fd=%d\n", fd);
printf("open success!\n");
char buf[2000] = {0};
int count = 0;
while(1)
{
int n = read(fd, buf, sizeof(buf));
if(n< 0)
{
perror("read");
return -1;
}
else if(n == 0) //读到文件的末尾了
{
break;
}
count += n;
printf("n=%d\n", n);
printf("buf:%s\n", buf);
memset(buf, 0 ,sizeof(buf));
}
printf("count=%d\n", count);
close(fd);
return 0;
}
2)输出结果
4.7 write(数据写入)
ssize_t write(int fd, const void *buf, size_t count);
功能:把内存中的数据写入到文件中
参数:
fd: 文件描述符
buf: 内存地址
count: 写入的字节数
返回值:
成功:实际写入的字节数n
n>=0
失败: n<0
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = open("./6.txt", O_CREAT|O_RDWR, 0666);
if(fd < 0)
{
perror("open");
return -1;
}
printf("fd=%d\n", fd);
printf("open success!\n");
char buf[100] = {0};
while(1)
{
gets(buf);
int n = write(fd, buf, strlen(buf));
printf("n=%d\n", n);
}
close(fd);
return 0;
}
2)输出结果
4.8 练习
1)复制文件内容:由1.txt复制到2.txt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
if(argc!=3)
{
printf("%s src_file dest_file\n", argv[0]);
return -1;
}
int fd1 = open( argv[1] , O_RDONLY);
if(fd1 < 0)
{
perror("open");
return -1;
}
int fd2 = open( argv[2] , O_WRONLY|O_CREAT|O_TRUNC, 0666);
if(fd2 < 0)
{
perror("open");
return -1;
}
printf("fd1=%d fd2=%d\n", fd1, fd2);
char buf[100] = {0};
while(1)
{
int n = read(fd1, buf, sizeof(buf));
if(n< 0)
{
perror("read");
return -1;
}
else if(n == 0) //读到文件的末尾了
{
break;
}
write(fd2, buf, n);
memset(buf, 0 ,sizeof(buf));
}
printf("copy success!\n");
close(fd1);
close(fd2);
return 0;
}
2)输出结果
4.9 lseek(定位文件指针)
off_t lseek(int fd, off_t offset, int whence);
功能:定位文件指针
参数:
fd: 文件描述符
offset: 偏移量
100:向后偏移100个字节
-100:向前偏移100个字节
whence: 基点
SEEK_SET:文件开头
SEEK_END:文件末尾
SEEK_CUR:文件当前位置
返回值:
成功:0
失败:-1
lseek(fd, 0, SEEK_END);
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd = open("./test.txt", O_CREAT|O_RDWR, 0666);
if(fd < 0)
{
perror("open");
return -1;
}
printf("fd=%d\n", fd);
printf("open success!\n");
write(fd, "hello", 5);
lseek(fd, 100, SEEK_END);
write(fd, "world", 5);
close(fd);
return 0;
}
2)输出结果
4.10 实践练习: 使用文件IO的函数加密图片 (音频、视频)
1)示例代码
/*===============================================
* 文件名称:jiami_1.c
* 创 建 者: 肖立生
* 创建日期:2022年07月22日
* 描 述:加密解密照片
================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
if(argc!=2)
{
printf("%s picture\n", argv[0]);
return -1;
}
int fd = open(argv[1], O_RDWR);
if(fd < 0)
{
perror("open");
return -1;
}
char buf[10] ={0};
read(fd, buf, 10);
int i;
for(i=0; i<5; i++)
{
buf[i] ^= buf[9-i];
buf[9-i] ^= buf[i];
buf[i] ^= buf[9-i];
}
lseek(fd, 0 , SEEK_SET);
write(fd, buf, 10);
printf("加密解密成功!\n");
close(fd);
return 0;
}
2)输出结果
未加密前
加密后
解密继续运行编译代码就能解密
4.11 目标操作符(opendir、readdir、closedir)
1.opendir(打开目录)
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:用来打开参数name 指定的目录, 并返回DIR*形态的目录流, 接下来对目录的读取和搜索都要使用此返回值
参数:
name:指定的目录
返回值:
成功: DIR *
失败: NULL
2.readdir(读取目录信息)
struct dirent *readdir(DIR *dirp);
功能:读取目录的信息
参数:
dirp: opendir函数的返回值
返回值:
成功: struct dirent *
失败: NULL(一直遍历,直到遍历目录完成返回NULL)
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all filesystem types */
char d_name[256]; /* filename */
};
3.closedor(关闭目录)
int closedir(DIR *dirp);
4.实践练习:实现查看目录下的文件
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, const char *argv[])
{
if(argc!=2)
{
printf("%s 目录名\n", argv[0]);
return -1;
}
DIR *dirp = opendir(argv[1]); //打开目录
if(dirp == NULL)
{
perror("opendir");
return -1;
}
while(1)
{
struct dirent *p = readdir(dirp); //读取目录信息
if( p == NULL )
{
break;
}
if( strncmp(p->d_name, ".", 1) == 0 )
{
continue;
}
printf("%s ", p->d_name);
}
puts(""); //printf("\n"); putchar('\n');
closedir(dirp); //关闭目录
return 0;
}
2)输出结果
4.12 文件信息函数(stat、getpwuid、getgrgid)
1.stat(查看文件信息)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
struct stat mybuf;
int stat(const char *path, struct stat *buf);
功能:查看文件信息
参数:
path:文件的路径名
buf: &mybuf
-rw-rw-r-- 1 farsight farsight 593 4月 19 09:26 01picture.c
-rw-rw-r-- 1 farsight farsight 537 4月 19 09:53 02readdir.c
-rw-rw-r-- 1 farsight farsight 161 4月 19 10:23 03stat.c
-rwxrw-rw- 1 farsight farsight 97893 4月 19 09:26 5.jfif
-rwxrwxr-x 1 farsight farsight 7357 4月 19 09:53 a.out
//查看设备号
major minor
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; 文件的权限、文件的类型 /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; 所属用户ID /* user ID of owner */
gid_t st_gid; 所属组ID /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; 文件的大小 /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; 最后一次访问时间 /* time of last access */
time_t st_mtime; 最后一次修改时间 /* time of last modification */
time_t st_ctime; 最后一次文件文件属性修改时间 /* time of last status change */
};
2.getpwuid(用户名)
struct passwd *getpwuid(uid_t uid);
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
3.getgrgid(组名)
struct group *getgrgid(gid_t gid);
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* group members */
};
4.实践练习:实现ls -l同等功能
文件类型
{
常规文件:S_ISREG '-'
目录:S_ISDIR 'd'
字符设备:S_ISCHR 'c'
块设备:S_ISBLK 'b'
管道:S_ISFIFO 'p'
套接字:S_ISSOCK 's'
符号链接:S_ISLNK 'l'
}
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <sys/sysmacros.h>
#include <grp.h>
#include <dirent.h>
int main(int argc, const char *argv[])
{
if(argc!=2)
{
printf("%s 文件路径名\n", argv[0]);
return -1;
}
DIR *dirp = opendir(argv[1]);
if(dirp == NULL)
{
perror("opendir failed!\n");
return -1;
}
chdir(argv[1]); //改变工作目录
while(1)
{
struct dirent *p= readdir(dirp);
if( p == NULL )
{
break;
}
while(NULL != p)
{
if( strncmp(p->d_name, ".", 1) == 0 )
{
break;
}
struct stat mybuf;
stat(p->d_name, &mybuf);
//文件类型
if(S_ISREG(mybuf.st_mode))
{
printf("-");
}
else if(S_ISDIR(mybuf.st_mode))
{
printf("d");
}
else if(S_ISCHR(mybuf.st_mode))
{
printf("c");
}
else if(S_ISBLK(mybuf.st_mode))
{
printf("b");
}
else if(S_ISFIFO(mybuf.st_mode))
{
printf("p");
}
else if(S_ISLNK(mybuf.st_mode))
{
printf("l");
}
else if(S_ISSOCK(mybuf.st_mode))
{
printf("s");
}
//文件权限
printf("%c", (mybuf.st_mode & (0x1<<8) )?'r':'-');
printf("%c", (mybuf.st_mode & (0x1<<7) )?'w':'-');
printf("%c", (mybuf.st_mode & (0x1<<6) )?'x':'-');
printf("%c", (mybuf.st_mode & (0x1<<5) )?'r':'-');
printf("%c", (mybuf.st_mode & (0x1<<4) )?'w':'-');
printf("%c", (mybuf.st_mode & (0x1<<3) )?'x':'-');
printf("%c", (mybuf.st_mode & (0x1<<2) )?'r':'-');
printf("%c", (mybuf.st_mode & (0x1<<1) )?'w':'-');
printf("%c", (mybuf.st_mode & (0x1<<0) )?'x':'-');
//链接数
printf(" %-2ld ", mybuf.st_nlink);
//用户名
struct passwd *m = getpwuid(mybuf.st_uid);
printf(" %-5s ", m->pw_name);
//组名
struct group *q = getgrgid(mybuf.st_gid);
printf(" %-5s ", q->gr_name);
//文件大小
printf(" %-5ld ", mybuf.st_size);
//最后一次修改的时间
struct tm *k = localtime(&mybuf.st_mtime);
printf(" %-2d %-2d %-2d:%-2d ", k->tm_mon+1,k->tm_mday,k->tm_hour,k->tm_min);
//文件名
printf("%s\n ",p->d_name);
p = readdir(dirp);
}
}
closedir(dirp);
return 0;
}
2)输出结果
ls -l
a.out
5.实践练习:实现ls -a功能
1)示例代码
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char *argv[])
{
//功能:实现打开目录,并读取目录中的文件的filename
//(1)opendir
DIR *pDir = opendir(argv[1]);
if(NULL == pDir)
{
perror("opendir error");
return -1;
}
printf("opendir ok!\n");
//(2)readdir
while(1)
{
struct dirent *pSD = readdir(pDir);
if(NULL == pSD)
{
break;
}
//使用pSD该结构体指针来访问结构体中的文件名
printf("%s\n",pSD->d_name);
}
closedir(pDir);
return 0;
}
2)输出结果
ls -a
a.out
三、 库的制作
1.什么是库
库是写好的,现有的,成熟的,可以复用的代码。本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。
/lib/i386-linux-gnu
/usr/include
库是一种加密的二进制文件
需要被操作系统载入内存运行
相比于可执行程序,它不可以直接运行
window 和 linux 都有自己的库,但是不兼容
Linux系统的库有两种,1. 静态库 2. 共享库(又叫动态库)
了解: 静态库 动态库
linux *.a *.so//window *.lib *.dll
2.静态库的制作和使用
2.1 制作
$ gcc -c xxx.c -o xxx.o
$ ar -crs libxxx.a xxx.o
静态库的命名规范:
必须以lib开头,紧跟库的名字,跟扩展名 .a
例如: libxxx.a
2.2 使用
$ gcc (-o xxx) main.c -L路径 -lxxx
-o xxx:编译生成文件,与a.out等同,可设置也可不设置
设置后运行:./xxx
未设置运行:./a.out
-L: 指定静态库所在的目录
-l: 指定静态库的名字 xxx部分
2.3 运行
优点:a.out 运行后不需要库,可以直接运行
缺点: 每个a.out都要包含库,体积较大, 浪费资源;
对程序更新,部署,发布带来麻烦;
3.动态库的制作和使用
3.1 制作
$ gcc -fPIC -c xxx.c -o xxx.o
$ gcc -shared -o libxxx.so xxx.o
动态库的命名规范:
必须以lib开头,紧跟库的名字,跟扩展名 .so
例如: libxxx.so
3.2 使用
$ gcc (-o xxx) main.c -L路径 -lxxx
-o xxx:编译生成文件,与a.out等同,可设置也可不设置
设置后运行:./xxx
未设置运行:./a.out
-L: 指定静态库所在的目录
-l: 指定静态库的名字 xxx部分
$ ldd a.out # 用于查看可执行程序依赖的动态库有哪些
3.3 运行
$ ./a.out # 会报错 (7146)
动态库的搜索方式(3种,任意选一种):
1.将动态库拷贝到 /lib/ 或者 /usr/lib/
$ sudo cp libxxx.so /usr/lib/
2.export LD_LIBRARY_PATH=. 或者so所在的路径 (临时情况)
3.pwd
cd /etc/ld.so.conf.d
ls sudo vi my.conf
添加路径
sudo ldconfig 生效
特点:在编译时不会链接到可执行文件中,只是再其中保存一个索引,在运行时,才真正的链接(动态),因此可执行程序体积小。
优点: a.out 体积较小, 节约资源; 只需要修改.so动态库,有利于程序的更新,部署,发布;
缺点:a.out 运行后需要库,不能直接运行。
4.静态库和动态库的区别
1.静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。
2.静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行;动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。
静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。
静态(函数)库
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。(在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库,因此体积较大)
动态(函数)库
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。(在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小)
四、进程
1.进程相关概念
程序:
用计算机语言对算法的具体实现,它是静态的
进程(process):
a.正在运行的程序,它是动态的(是一个程序一次执行的过程)
b.资源管理的最小单位 (每一个进程都分配一个虚拟的4G内存)
c.是程序执行的过程,包括创建、调度和消亡
虚拟内存空间:
a.核空间:1G
b.用户空间:0-3Gc.内核:3G-4G
堆、栈 、代码段 、数据段 、不可访问空间
进程号PID:唯一标识一个进程
进程结束后,用户空间会随着进程的结束而释放,但内核空间不会,这1G空间需要创建它的父进程来回收,否则会称为僵尸进程(Bug)
2.进程和程序的区别
本质的区别:
1) 程序(Program):
- 程序是一系列指示计算机执行任务的命令,通常以源代码或目标代码的形式存在。
- 它是静态的,存储在存储器中,直到被加载并执行。
- 程序由计算机指令和数据组成,这些指令和数据按照特定的顺序排列,用来解决问题或完成特定的任务。
- 程序本身不具备执行能力,需要通过计算机的处理器来执行。
2) 进程(Process):
- 进程是程序在计算机中执行的过程,是系统进行资源分配和调度的基本单位。
- 进程是动态的,它包括程序计数器、寄存器和变量的当前值等,是程序在处理器上的一次动态执行过程。
- 进程具有独立的执行流,每个进程都有自己的虚拟地址空间、内存、数据栈以及其他用于跟踪执行状态的辅助数据。
- 操作系统通过进程来管理计算机的多任务处理,它可以同时运行多个进程,每个进程都好像在独立运行一样。
简而言之,程序是静态的代码集合,而进程是程序在执行过程中动态的表现。当程序被加载到内存中并由操作系统执行时,它就变成了一个进程。
3.进程的通用内存管理
正文段、用户数据段、系统数据段
在操作系统中,进程的内存通常被分为几个不同的段或区域,每种段都有其特定的用途。正文段、用户数据段和系统数据段是进程内存中的三种主要段:
1)正文段(Text Segment):
- 正文段也称为代码段,包含了进程执行的机器指令。
- 它是只读的,因为一旦程序被加载到内存中,其机器指令不应被修改。
- 正文段通常包含了程序的入口点,即程序开始执行的地址。
- 在某些操作系统中,如Mach或Windows,正文段可能被分为多个部分,如只读的代码部分和可写的初始化代码部分。
2)用户数据段(User Data Segment):
- 用户数据段用于存储进程运行时创建的数据,如局部变量、函数参数、返回值等。
- 它供程序中的函数使用,并且通常在函数调用期间由程序管理。
- 用户数据段是可读写的,因为程序需要读写这些数据来执行任务。
3)系统数据段(System Data Segment):
- 系统数据段用于存储系统运行时所需的信息,如全局变量、静态变量等。
- 它也可能包含某些操作系统特定的信息,如线程局部存储(TLS)数据。
- 系统数据段通常是只读的,以保证这些数据的一致性和安全性。除了这三种主要段,还有一些其他的内存段可能存在于进程的地址空间中,例如堆(Heap),它是用于动态分配内存的区域,通常由程序在运行时请求和释放。堆栈(Stack)则用于存储函数调用的上下文信息,局部变量等,它在函数调用期间由调用者自动管理。
不同的操作系统和编译器可能会有不同的内存段划分方式,但上述提到的正文段、用户数据段和系统数据段是较为通用的分类。这些段的划分有助于提高程序的可维护性,确保程序的正确执行,并为操作系统提供有效的内存管理手段。
以下是进程内存管理的一些关键点(涉及到进程如何申请、使用和释放内存资源):
内存分配、内存映射、内存保护、内存共享、内存交换(Swapping)和分页(Paging)、内存释放、内存管理单元(MMU)
4.进程的类型
1.交互进程(ctrl+z / jobs -l / bg / fg / kill -l /kill -9 PID /ps -ajx)
1)shell命令进程,文本编辑器(vim),图形应用程序等
2)既有前台进程,又有后台进程
3) fg+ 作业号 :可以将后台运行的程序,切换到前台来运行/*****************************************/
ctrl+z : 让进程变成后台进程
jobs -l: 查看后台的进程,叫做作业 16674:作业号
bg % 作业号:使后台进程恢复到前台,不能被ctrl+c结束(关闭终端即可)
fg % 作业号:使后台进程恢复到前台,能被ctrl+c结束
kill -l: 查看信号的种类
kill -9 PID: 结束相应的进程
ps -ajx: 查看进程的属性
/***************************************/2.批处理进程(运维)
该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
3.守护进程(1 init)
该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
5.不同系统查看进程
windos
任务管理器
Linux
ps -aux
显示不了状态
ps -ef
查看所有进程及其PID(进程号),系统时间,命令详细目录,执行者等
ps axj
查看系统当中的进程,也就是正在运行的程序
top
动态显示系统中运行的程序(一般每隔5s刷新一次)
pstree
以树形结构显示系统当中的进程
kill
输出特定的信号给指定PID(进程号)的进程‘
-l 列出所有可用的信号名称.
6.进程的运行状态
ps -ajx
就绪态:进程已获得除CPU外的所有必要资源,只等待CPU时的状态。一个系统会将多个处于就绪状态的进程排成一个就绪队列。
运行态(R) TASK_RUNNING: 进程已获CPU,正在执行。单处理机系统中,处于执行状态的进程只一个;多处理机系统中,有多个处于执行状态的进程。此时进程或者正在进行,或者准备运行内核调度程序到CPU上执行 running
等待态 (阻塞态):正在执行的进程由于某种原因而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。(这种状态又称等待状态或封锁状态)
可中断的等待态 S (标准输入,sleep()) (TASK_INTERRRUPTIBLE):如果进程收到信号会醒来 ctrl+c
不可中断的等待态 D (TASK_UNINTERRUPTIBLE):如果进程收到信号不会醒来停止态T:此时进程被中止SIGSTOP
死亡态X:已终止的进程、僵尸进程 但还在进程向量数组中占有一个task_struct结构
task_struct{
pid_t pid;
R; ...
};
僵尸态(Z)EXIT_ZOMIE
Linux系统中,进程可以分为三种基本状态,这三种状态定义在`POSIX.1-2001`标准中,它们是:
1. **运行(Running)**:
- 进程正在CPU上执行。
- 在多道程序环境中,可能有许多进程处于运行状态,但任何时刻只能有一个进程在处理器上执行。
2. **阻塞(Blocked)**:
- 进程在等待某个事件发生,比如等待输入/输出操作完成。
- 进程在等待某些资源,如打开的文件或其他系统资源。
- 阻塞状态的进程不会使用CPU。
3. **就绪(Ready)**:
- 进程已经准备好开始执行,但尚未被调度执行。
- 进程可能因为优先级较低或其他原因而等待被CPU调度。
- 在多道程序设计系统中,就绪状态的进程等待被CPU选中并开始执行。
除了这三种基本状态,还有一种终止状态(Terminated),表示进程已经完成执行并已从系统中移除。
Linux进程的状态转换通常涉及以下几种变迁:
- **运行到阻塞**:进程在执行过程中,请求的资源不可用,如进行I/O操作时,CPU会让出,进程状态从运行变为阻塞。
- **阻塞到就绪**:进程等待的事件发生,如I/O完成,进程状态从阻塞变为就绪,等待CPU调度。
- **就绪到运行**:进程被调度器选中,从就绪状态开始执行。
- **运行到就绪**:在多任务系统中,调度器可能会因为时间片到、更高优先级任务到达等原因,中断当前运行的进程,让其他就绪状态的进程运行,此时当前进程状态从运行变为就绪。
- **运行到终止**:进程完成其任务,自行终止或者因为某些错误被操作系统终止,状态变为终止。
这些状态转换是操作系统调度进程、管理资源的基础,确保了系统的高效运行。在Linux系统中,可以使用`ps`命令等工具查看进程的状态信息。
7.进程的优先级
1.进程的优先级范围
-20到19,数字越大,优先级越低
2.nice:以某种优先级执行我们的程序
3.renice :可以修改正在运行程序的优先级< 高优先级
N 低优先级
L 有些页被锁进内存
s 会话组组长+ 位于前台的进程组
l 多线程,克隆线程
ctrl + alt + f1 - f6 : 打开字符终端
用户名:farsight 密码:1ctrl + alt +F1:进入界面
ctrl + alt +F5
ctrl + alt +F6
为了多用户使用计算机
结束字符终端: alt + f7
top
top -p PID : 动态查看进程状态
renice -5 PID : 改变进程的NI值(默认0)
8.进程相关函数
fork/exit 创建进程、退出进程
wait/waitpid 回收进程资
8.1 fork(进程创建)
#include <unistd.h>
#include <sys/types.h>
pid_t pid; //进程号
pid_t fork(void);
功能:创建进程
参数:无
返回值:
pid < 0 :创建进程失败
pid == 0 : 子进程
pid > 0 : 父进程
优缺点
好处:可以同时处理多项工作,互不干扰
缺点:进程是资源分配的最小单元,每创建一个进程,虚拟出来4G内存空间,子进程几乎复制了父进程所有的资源
1.进程执行
从fork函数往下分为两个进程开始运行。
父进程和子进程执行顺序是随机的。
父子执行顺序:不确定
若父进程先结束,子进程未结束,子进程就会称为孤儿进程
若子进程先结束,父进程未结束,但父进程没有回收子进程的内核资源,子进程会称为僵尸进程
2.fork函数特性
1.子进程创建时,几乎拷贝了父进程全部内容,包括代码段、数据段、堆栈段、文件描述符、虚拟地址空间
2.同一个父进程创建的子进程都是属于同一个进程组
pkill -9 -g PGID
3.进程是管理资源的最小单位
3.示例
3.1 fork函数进程执行顺序
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid; //进程号
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
else if(pid == 0) //子进程
{
while(1)
{
printf("son is running!\n");
sleep(2);
}
}
else{ //父进程
while(1)
{
printf("father is running!\n");
sleep(2);
}
}
return 0;
}
2)输出结果
8.2 exit(进程主动退出:自带清理缓冲区)
#include <stdlib.h>
void exit(int status); //有缓存
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int fun(void)
{
printf("aaaaaaa\n");
exit(0);
//return 0; //输出结果:111111 aaaaaaa 222222
}
int main(int argc, const char *argv[])
{
printf("111111\n");
fun();
printf("222222\n");
return 0;
}
2)输出结果
8.3 _exit(进程主动退出:不清理缓冲区)
//系统调用函数
#include <unistd.h>
void _exit(int status); //无缓存
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int fun(void)
{
printf("aaaaaaa\n");
_exit(0);
}
int main(int argc, const char *argv[])
{
printf("111111\n");
fun();
printf("222222\n");
return 0;
}
2)输出结果
8.4 wait(避免僵尸进程:阻塞回收)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:
父进程等待子进程结束,回收它的资源
函数参数:
status指向对象用来保存子进程退出时状态
status为空,表示忽略子进程退出时状态
status不为空,表示保存子进程退出时状态
函数返回值
成功:子进程的进程号
失败:-1
WEXITSTATUS(status) 获取子进程返回值
WIFEXITED(status) 判断子进程是否正常结束
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1); //return -1
}
else if(pid == 0 )//子进程
{
int n = 5;
while(n--)
{
printf("son is running!\n");
sleep(1);
}
exit(15);
}
else{ //父进程
int status;
wait(&status);
printf("%d\n", WEXITSTATUS(status)); //获取子进程的返回值
printf("%d\n", WIFEXITED(status)); //判断子进程是否正常结束
while(1);
}
return 0;
}
2)输出结果
子进程先与父进程退出---父进程未回收资源---子进程会变成僵尸进程
危害:占用进程号、内存空间、PCB进程控制块等
解决:wait / waitpid / 信号
注意:任何进程结束都会变成僵尸进程,只是时间有长有短父进程先与子进程退出---子进程会变成孤儿进程---被init进程接管(收养)
init进程:系统启动后运行的第一个用户空间进程,pid=1,会定期扫描系统,收养孤儿进程。
注意:孤儿进程一般没什么危害
8.5 waitpid(避免僵尸进程:非阻塞回收)
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:
父进程自动回收子进程结束后的资源
参数:
pid: -1 任意进程
status: NULL
options:
0 阻塞
WNOHANG 非阻塞
eg:
wait(NULL); 阻塞等待任意一个子进程退出,不接受子进程退出的状态
waitpid(-1, NULL, 0); == wait(NULL);
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1); //return -1
}
else if(pid == 0 )//子进程
{
int n = 5;
while(n--)
{
printf("son is running!\n");
sleep(1);
}
exit(15);
}
else{ //父进程
//阻塞
//waitpid(-1, NULL, 0);
//sleep(15);
//非阻塞
waitpid(-1, NULL, WNOHANG);
while(1)
{
printf("xxxx\n");
sleep(1);
}
}
return 0;
}
2)输出结果
8.6 vfork()和fork()区别
fork(): 子进程拷贝父进程的数据段,代码段,且父子进程执行次序不确定
vfork(): 子进程与父进程共享数据段,保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行在使用vfork函数在调用exec()或exit()之前,子进程依赖于父进程的进一步动作,将会导致死锁。
8.7 exec函数族
1.概念
fork()子进程为了执行新的程序可直接在子进程if中写入新程序代码,但不够灵活
exec族函数可以把一个编译好的的可执行程序直接加载运行,将子进程单独编译为可执行文件,而将父进程作为主程序并创建子进程用exec来执行已编译好的可执行文件。
函数族提供了一种在进程中启动另一个程序执行的方法。 它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件:a.out,也可以是Linux下任何可执行脚本文件。
比如bash用到了exec函数来执行我们的可执行文件:ls等。
2. 在Linux中使用exec函数族主要有以下两种情况
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生
3.函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
返回值:
成功不返回
失败返回 -1 更新 errno
注意:
exec函数的参数表传递方式以函数名的第五位字母来区分:
字母为"l"(list)的表示逐个列举的方式;
字母为"v"(vertor)的表示将所有参数构造成指针数组传递;
以p结尾的函数可以只给出文件名
以"e"(enviromen)结尾的两个函数execle、execve就可以在envp[]中设置当前进程所使用的环境变量
使用execle和execve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量
3.1 execl(逐个列举)
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("hello world\n");
int ret = execl("./test", "test", NULL);
if(ret == -1)
{
perror("execl");
exit(-1);
}
printf("22222222222222\n");
return 0;
}
2)输出结果
3.2 execv(所有参数构造成指针数组传递)
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("hello world\n");
char *arg[] = {"test", NULL};
int ret = execv("./test", arg);
if(ret == -1)
{
perror("execv");
exit(-1);
}
printf("22222222222222\n");
return 0;
}
2)输出结果
3.3 execlp(能实现ls -l等快捷命令)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
int ret = execlp("ls","ls","-l",NULL);
if(ret == -1)
{
perror("execl");
exit(-1);
}
printf("02222222\n");
return 0;
}
2)输出结果
3.4 execvp(指针传递)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
char *arg[]={"ls","-l",NULL};
int ret = execvp("ls",arg);
if(ret == -1)
{
perror("execl");
exit(-1);
}
printf("02222222\n");
return 0;
}
2)输出结果
3.5 execle(环境变量)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
//设置环境变量
char *env[]= {"PATH=.",NULL};
int ret = execle("./test","test",NULL,env);
if(ret == -1)
{
perror("execl");
exit(-1);
}
printf("02222222\n");
return 0;
}
2)输出结果
3.5 execve(环境变量指针传递)
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
char *arg[] = {"time",NULL};
//设置环境变量
char *env[]= {"PATH=.",NULL};
int ret = execve("./time",arg,env);
if(ret == -1)
{
perror("execl");
exit(-1);
}
printf("02222222\n");
return 0;
}
事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用
8.8 strtok(将字符串分割成一个片段)
#include <string.h>
char * strtok(char *s, const char *delim);
函数说明:
strtok()用来将字符串分割成一个个片段.
参数s 指向欲分割的字符串;
参数delim 则为分割字符串,当strtok()在参数s 的字符串中发现到参数delim 的分割字符时则会将该字符改为\0 字符.
在第一次调用时,strtok()必需给予参数s 字符串, 往后的调用则将参数s 设置成NULL. 每次调用成功则返回下一个分割后的字符串指针.
参数:
S:指向欲分割的字符串————》NULL
delim:为分割字符串
返回值:
返回下一个分割后的字符串指针, 如果已无从分割则返回NULL.
1)示例代码(实现bash的效果)
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define N 64
int main(int argc, char *argv[])
{
char buf[N] = {0}; //定义并初始化一个字符串数组
char *arg[N]; //定义一个指针数组
pid_t pid;
int i = 0, j;
while (1)
{
printf("linux@ubuntu:~$");
fgets(buf, N, stdin);//ls -l -a //等待用户从键盘输入,回车后将输入的内容写到buf,包括 \n,
//此时,buf数组中以\0结尾,\0 前面是 \n,显然 \n 不是命令
buf[strlen(buf)-1] = '\0'; //将buf数组中的\0前面的那个\n用\0进行覆盖
if (strcmp(buf, "exit") == 0) //看输入的是否为exit,注:没有对空格进行处理
break;
if ((pid = fork()) == -1) //创建子进程
{
perror("fork");
exit(-1);
}
if (pid == 0)
{
/*
下面是利用strtok函数将buf数值进行解析,把命令和参数分别解析出来,strtok函数的使用参见
C语言函数大全:下面是部分内容
头文件:#include <string.h>
定义函数:char * strtok(char *s, const char *delim);
函数说明:strtok()用来将字符串分割成一个个片段. 参数s 指向欲分割的字符串, 参数delim 则为分割字符串,当strtok()在参数s 的字符串中发现到参数delim 的分割字符时则会将该字符改为\0 字符. 在第一次调用时,strtok()必需给予参数s 字符串, 往后的调用则将参数s 设置成NULL. 每次调用成功则返回下一个分割后的字符串指针.
返回值:返回下一个分割后的字符串指针, 如果已无从分割则返回NULL.
范例
#include <string.h>
#include <stdio.h>
int main(void)
{
char s[] = "ab-cd : ef;gh :i-jkl;mnop;qrs-tu: vwx-y;z";
char *delim = "-: ";
char *p;
printf("%s ", strtok(s, delim));
while((p = strtok(NULL, delim)))
printf("%s ", p);
printf("\n");
return 0;
}
执行结果:
ab cd ef;gh i jkl;mnop;qrs tu vwx y;z //-与:字符已经被\0 字符取代
*/
arg[i] = strtok(buf, " ");
do
{
++i;
arg[i] = strtok(NULL, " ");
}while(arg[i] != NULL);
for (j = 0; j < i; j++)
printf("%s\n", arg[j]);
// char * arg[] = {"ls", "-l", "-a", NULL};
if (-1 == execvp(arg[0], arg)) //在子进程中调用execvp函数,子进程的三段被替换
{
perror("execvp");
exit(-1);
}
}
else
wait(NULL); //父进程等待子进程就是,即等待argv[0]中指向的命令执行完
}
return 0;
}
2)输出结果
9.daemon(守护进程)
9.1 什么是守护进程
守护进程即为Daemon进程,是linux三种进程之一,在系统启动时运行,关闭时结束;在linux中与用户交互的界面叫终端,从终端运行起来的程序都依附于这个终端,当终端关关闭时,相应的进程都会被关闭,守护进程可以突破这个限制。
特点:
- 始终运行在后台,后台服务进程
- 独立于任何终端(和终端无关)比如:init进程 pid=1 开机运行 关机才结束
- 周期性地执行某种任务或等待处理特事件
9.2 什么是会话?
a.Linux是以会话(session),进程组的方式管理进程.每个进程属于一个进程组,子进程同属于该进程组.
b.会话是由一个或者多个进程组的集合,通常用户打开一个终端,系统就会创建一个会话,所有通过该终端运行都属于这个会话,shell进程--->会话组的组长,一个会话最多打开一个控制终端,当控制终端结束时,所有的进程也跟着结束。
9.3 守护进程创建流程
1. 创建子进程,父进程退出,fork() 为了成为叫后台进程
fork(void);
2. 在子进程中创建新会话,(脱离原来的会话,成为新的会话组长)
#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
3. 修改工作目录
#include <unistd.h>
int chdir(const char *path);
4. 修改umask (增加安全性)
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
5. 关闭文件描述(回收资源)
#include <unistd.h>
close();
9.4 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
else if(pid == 0)
{
setsid(); //2.开启新的会话
chdir("/tmp"); //3.改变工作目录
umask(0); //4.增加安全性
close(0);
close(1);
close(2); //5.关闭文件描述符
FILE *fp;
while(1)
{
fp = fopen("/tmp/test.txt", "a");
time_t t;
time(&t);
fprintf(fp, "%s", ctime(&t));
fflush(fp);
sleep(1);
}
}
else{ //父进程
exit(0); //1.父进程退出
}
return 0;
}
五、线程
1.学线程的优点
1) 由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大
2) 为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程
3) 在同一个进程中创建的线程共享该进程的地址空间(除了栈区之外)
2.线程的概念
每个用户进程有自己的虚拟地址空间
系统为每个用户进程创建一个 task_struct 来描述该进程 struct task_struct
task_struct 和地址空间映射表一起用来表示一个进程
由于进程的虚拟地址空间是私有的,因此进程切换开销很大
为了提高系统的性能,linux引入轻量级进程, 起名为线程
在同一个进程中创建的线程共享该进程的地址空间
Linux里同样用task_struct来描述一个线程。
线程和进程都参与统一的调度
总结:
通常线程指的是共享相同虚拟地址空间的多个任务
使用多线程, 大大提高了任务切换的效率
线程不需要虚拟内存,为什么?
每个进程中至少有一个线程,就是主线程,还可以产生多个线程
共享4G内存空间,线程切换只需要虚拟CPU(寄存器)
同样用task_struct来描述一个线程,线程和进程都参于统一的调度
进程代表资源分配的最小单位
线程是最小的调度单位
同一个地址空间中的多个任务
3.线程与进程的区别
线程和进程是操作系统中用于并发执行的两个基本概念,它们之间有着本质的区别:
1. 进程(Process):
- 进程是操作系统进行资源分配和调度的基本单位。它是一个具有独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度的一个独立单位。
- 每个进程都拥有独立的地址空间,一个进程崩溃后,在保护模式下不会影响到其他进程,因为系统为每个进程提供了独立的内存空间。
- 进程间的通信(IPC,Inter-Process Communication)需要依赖特定的机制(例如管道、消息队列、信号量、共享内存等)。
2. 线程(Thread):
- 线程是进程的执行单元,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,并且线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可以与同属一个进程的其他线程共享进程所拥有的全部资源。
- 一个进程可以有多个线程,同一个进程中的线程间可以直接通信。
- 线程有时被称为轻量级进程(Lightweight Process),它们的创建、撤销和切换比进程更快。
在多线程环境中,一个进程内的多个线程可以共享进程资源,如内存堆、打开的文件等,但每个线程有自己的执行堆栈和程序计数器等少量资源。线程间的通信比进程间通信要简单和高效,因为它们不需要操作系统介入,只需要在进程的地址空间内进行数据交换即可。
多线程设计可以让程序更有效地利用多核处理器的计算资源,提高程序的响应速度和性能,特别是在用户界面程序和服务器程序中应用广泛。然而,多线程也引入了诸如线程安全、死锁、竞态条件等复杂性问题,需要开发者谨慎设计并使用同步机制来避免。
4.线程的相关函数
4.1 pthread_create(创建线程)
#include <pthread.h>
pthread_t tid; //线程号
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:
thread: &tid 线程号的地址
attr: NULL 默认参数(缺省参数) 线程的属性
start_routine: 线程函数的函数名
arg: 给线程函数传递的参数 如果不传参 写NULL
返回值:
成功: 0
失败: 错误码 -1
Compile and link with -pthread.
使用-pthread编译和链接。
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
//线程函数
void *fun(void * arg)
{
while(1)
{
printf("fun is running\n");
sleep(2);
}
}
int main(int argc, char *argv[])
{
pthread_t tid; //线程号
int ret = pthread_create((&tid),NULL,fun,NULL);
if(ret == -1)
{
perror("pthread_create");
return -1;
}
while(1)
{
printf("tid is running\n");
sleep(2);
}
return 0;
}
2)输出结果
4.2 pthread_join(回收线程资源:阻塞)
int pthread_join(pthread_t thread, void **retval);
功能:主线程等待子线程结束,回收它的资源(阻塞)
参数:
thread: 线程号
retval: 子线程的返回值
返回值:
成功:0
失败:-1
a.函数在线程退出时,用来清理线程资源。
b.会阻塞调用方,直至pthread_join所指的线程退出
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
//线程号
void *fun1(void * arg)
{
int n =5;
while(n--)
{
printf("fun is running\n");
sleep(1);
}
}
int main(int argc, char *argv[])
{
pthread_t tid;//线程号
int ret = pthread_create((&tid),NULL,fun1,NULL);
if(ret == -1)
{
perror("pthread_create");
return -1;
}
printf("aaaaaaaaaaaa\n");
pthread_join(tid,NULL);
printf("bbbbbbbbbbbbb\n");
while(1);
return 0;
}
2)输出结果
4.3 pthread_detach(回收线程资源:非阻塞)
int pthread_detach(pthread_t thread);
功能:主线程和子线程分离开来,自动回收资源 (非阻塞)
参数:
thread: 线程号
返回值:
成功:0
失败:-1
a.因为线程默认的状态是结合态的,所以,可以通过pthread_detach函数来设置线程为分离态。
b.使用pthread_detach函数后,使线程处于分离态;
c.使用pthread_detach函数后,线程在退出后,会自己清理资源
d.相较pthread_join,使用pthread_detach函数不会阻塞主线程,但是无法获取线程的返回值。
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
//线程函数
void *fun(void *arg)
{
int n = 5;
while(n--)
{
printf("fun is running!\n");
sleep(1);
}
}
void *fun1(void *arg)
{
int n = 8;
while(n--)
{
printf("fun1 is running!\n");
sleep(1);
}
}
void *fun2(void *arg)
{
int n = 12;
while(n--)
{
printf("fun2 is running!\n");
sleep(1);
}
}
int main(int argc, const char *argv[])
{
pthread_t tid, tid1, tid2; //线程号
int ret = pthread_create(&tid, NULL, fun, NULL);
if(ret == -1)
{
perror("pthread_create");
return -1;
}
printf("aaaaaaaaaaa\n");
//pthread_join(tid, NULL);
pthread_detach(tid);
pthread_create(&tid1, NULL, fun1, NULL);
pthread_detach(tid1);
pthread_create(&tid2, NULL, fun2, NULL);
pthread_detach(tid1);
printf("bbbbbbbbbbbb\n");
while(1);
return 0;
}
2)输出结果
4.4 pthread_exit(线程的退出:主动退出)
#include <pthread.h>
void pthread_exit(void *retval);
功能:结束子线程
参数:
retval : void * 线程退出返回的值
返回值:
成功:0
失败:-1
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
//线程函数
void *fun(void * arg)
{
char *p = (char *)arg;
printf("%s\n",p);
pthread_exit(arg);//相当于return arg;
}
int main(int argc, char *argv[])
{
pthread_t tid;//线程号
char buf[100]= "hello";
pthread_create(&tid,NULL,fun,(void *)buf);
void *arg;
pthread_join(tid,&arg);
printf("%s %s\n",buf,(char *)arg);
while(1)
return 0;
}
2)输出结果
4.5 pthread_cancel(线程的退出:被动退出)
#include <pthread.h>
int pthread_cancel(pthread_t thead)
函数参数:
thread:要取消的线程
返回值:
成功:0
出错:-1
1)示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *fun(void * arg)
{
while(1)
{
printf("fun is running\n");
sleep(1);
}
}
int main(int argc, char *argv[])
{
pthread_t tid;
char buf[100] = {0};
pthread_create((&tid),NULL,fun,NULL);
pthread_detach(tid);
while(1)
{
fgets(buf,sizeof(buf),stdin);
if(strncmp(buf,"q",1) == 0)
{
pthread_cancel(tid);
}
if(strncmp(buf,"Q",1) == 0)
{
exit(0);
}
}
}
2)输出结果
4.6 练习
创建子线程一:模拟 cat filename 并返回执行情况 ,成功返回OK,失败返回ERROR
创建子线程二:模拟 ls pathname 并返回执行情况 , 成功返回OK,失败返回ERROR
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//创建线程的函数: pthread_create
//等待线程的结束的函数 pthread_join pthread_detach()
//线程的退出: pthread_exit() pthread_cancel()
//线程一:模拟 cat filename
//线程二:模拟 ls pathname
void * mycat(void *arg)
{
//标准IO:fopen fclose fread fwrite fgetc fputc fgets fputs fprintf fscanf feof fseek ftell
//文件IO:
//文件:open close read write lseek
//目录: opendir closedir readdir stat(ls -l) 获得文件的属性
//&fd = arg;将主线程的&fd给arg
//通过arg拿到原来的fd
//线程1:模拟cat filename
//将你打开文件描述符传递给我
int *pfd = (int *)arg;
int fd =*pfd;
printf("thread1 fd = %d\n",fd);
ls show ok****
//读取fd指向文件中的内容
char buf[20]={'\0'};
printf("-------------------pthread1---------------\n");
while(1)
{
memset(buf,'\0',sizeof(buf));
//read() 参数1:文件描述符 参数2:缓冲区的地址 参数3:读取的字节数
int n=read(fd,buf,sizeof(buf)-1);
if(n==0)
{
printf("---------------------\n");
pthread_exit("file read over");
}
else if (n<0){
pthread_exit("file read error");
}
printf("%s",buf);
}
}
void * myls(void *arg)
ls show ok****
{
//线程2:模拟 ls pathname
//将打开文件夹描述符传递给我
DIR *pDir = (DIR *)arg;
printf("thread2 pDir = %p\n",pDir);
if(NULL == pDir)
{
pthread_exit("pathname no exit");//子线程给主线程传递
}
struct dirent * pd = NULL;
printf("**********pthread 2*************************\n");
while(1)
{
pd = readdir(pDir);
if(NULL == pd)
{
printf("\n**************************************\n");
pthread_exit("ls show ok");
}
printf("%s ",pd->d_name);//显示文件名
}
}
int main(int argc, const char *argv[])
{
pthread_t pth1,pth2;
//2.以只读方式
int fd = open("1.txt",O_RDONLY);
printf("main fd = %d\n",fd);
//打开目录
DIR *pDir = opendir("./");
printf("main pdir = %p\n",pDir);
//创建线程1,模拟cat filename
//参数4:void * 任何数据类型的地址
pthread_create(&pth1,NULL,mycat,&fd);//主线程中的值传递给子进程
//创建线程2,模拟ls pathname
pthread_create(&pth2,NULL,myls,pDir);
//接受子线程传过来的值
char *result = NULL;
pthread_join(pth1,(void**)&result);
printf("pth1 result=%s****\n",result);
pthread_join(pth2,(void **)&result);
printf("pth2 result=%s****\n",result);
return 0;
}
优点:线程间很容易进行通信 通过全局变量实现数据共享和交换
缺点:多个线程同时访问共享对象时需要引入同步和互斥机制
六、同步和互斥
同步和互斥 :保护共享资源,避免竟态
同步:多个任务按理想的顺序/步调来进行
互斥:不能同时访问
都是为了避免竟态:多个任务同时访问共享资源
1.同步
1.1 什么是同步?
多线程访问临界资源,按照某种顺序执行
无名信号量:用于线程间的同步
有名信号量:用于进程间的同步----信号灯集
1.2 线程的同步---信号量
1.概念
a.信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问.
b.当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现他们之间的顺序执行
2.sem_init/wait/post
#include <semaphore.h>
sem_t sem; //信号量的变量
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem: &sem 信号量的变量的地址
pshared: 0 :线程 1 :进程
value: 信号量的初值
返回值:
成功:0
失败:-1
int sem_wait(sem_t *sem); //-1
//如果初值为0,会阻塞
参数:
sem:信号量
当信号量值>0 会将信号量的值减1
当信号量值==0 阻塞线程
返回值:
成功:0
出错:-1
int sem_post(sem_t *sem); //+1
参数:
sem:信号量
将信号量的值加1,同时唤醒等待的线程
返回值:
成功:0
出错:-1
1)示例代码(主线程等待子线程结束)
/*===============================================
* 描 述:主线程等待子线程结束
================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem; //信号量
void *fun(void * arg)
{
int n = 5;
while(n--)
{
printf("fun is running\n");
sleep(1);
}
sem_post(&sem); //+1
}
int main(int argc, char *argv[])
{
sem_init(&sem,0,0);
pthread_t tid;
pthread_create(&tid,NULL,fun,NULL);
pthread_detach(tid);
printf("111111\n");
sem_wait(&sem); //-1
printf("222222\n");
while(1);
return 0;
}
2)输出结果
3.实践练习(计算键盘输入字符串的个数 )
buf: 全局变量
主线程:输入字符串
子线程:打印字符串的长度
1)示例代码
/*===============================================
* 描 述:计算键盘输入字符串的个数
================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem; //信号量
char buf[100] = {0};
void *fun(void * arg)
{
/* int sum = 0;
for(int i = 0;buf[i];i++)
{
sum += buf[i];
}
printf("%d\n",sum);
sem_post(&sem); //+1*/
while(1)
{
sem_wait(&sem);
printf("%ld\n",strlen(buf)-1);
}
}
int main(int argc, char *argv[])
{
sem_init(&sem,0,0);
pthread_t tid;
pthread_create(&tid,NULL,fun,NULL);
pthread_detach(tid);
while(1)
{
fgets(buf,sizeof(buf),stdin);
sem_post(&sem);
}
return 0;
}
2)输出结果
4.实践练习(用多线程进行读写操作:文件拷贝)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
sem_t sem1,sem2;
char buf[100] = {0};
int flag = 0;
int n = 0;
void *read_data(void *arg)
{
//先读 从1.txt读取
int fd = open("./1.txt", O_RDONLY);
while(1)
{
sem_wait(&sem2); //灯B -1
// printf("111111111111\n");
// sleep(1);
n = read(fd, buf, sizeof(buf));
if(n < 0)
{
perror("read");
return NULL;
}
else if(n == 0)
{
flag = 1;
close(fd);
break;
}
sem_post(&sem1); //灯A +1
}
}
void *write_data(void *arg)
{
//再写到2.txt
int fd = open("./2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
while(1)
{
sem_wait(&sem1); //灯A -1
//printf("22222222222222\n");
//sleep(1);
write(fd, buf, n);
if(flag == 1)
{
close(fd);
break;
}
sem_post(&sem2); //灯B +1
}
}
int main(int argc, const char *argv[])
{
sem_init(&sem1, 0, 0); //灯A
sem_init(&sem2, 0, 1); //灯B
pthread_t tid1,tid2;
pthread_create(&tid1, NULL, read_data, NULL);
pthread_detach(tid1);
pthread_create(&tid2, NULL, write_data, NULL);
pthread_detach(tid2);
while(1);
return 0;
}
2.互斥
2.1 什么是互斥?
多线程访问临界资源时,没有顺序而言,若线程A访问临界资源,不允许其他线程访问资源。
引入互斥锁:用来保证共享数据操作的完整性。
2.2 线程的互斥---互斥锁
1. pthread_mutex_init/lock/unlock
1.定义互斥锁 (全局)
pthread_mutex_t myMutex; //互斥锁的变量
2.初始化锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化互斥锁
参数:
mutex: &mutex互斥锁
mutexattr:互斥锁属性(NULL表示缺省属性)
返回值:
成功:0
出错:-1
3.上锁(申请互斥锁)
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex)
参数:
mutex:互斥锁
返回值:
成功:0
出错:-1
4.解锁
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex)
参数:
mutex:互斥锁
返回值:
成功:0
出错:-1
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
int i, value1, value2;
pthread_mutex_t mutex; //互斥锁的变量
void *fun1(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
i++;
value1 = i;
value2 = i;
pthread_mutex_unlock(&mutex);
}
}
void *fun2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(value1 != value2)
{
printf("%d %d %d\n",i, value1, value2);
}
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, const char *argv[])
{
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
pthread_t tid1,tid2;
pthread_create(&tid1, NULL, fun1, NULL);
pthread_detach(tid1);
pthread_create(&tid2, NULL, fun2, NULL);
pthread_detach(tid2);
while(1);
return 0;
}
2.实践练习(用多线程进行读写操作:文件拷贝)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
char buf[100] = {0};
int fd1,fd2,n=0;
pthread_mutex_t mutex; //互斥锁的变量
void *myread(void *arg)
{
fd1 = open( "./1.txt" , O_RDONLY);
while(1)
{
pthread_mutex_lock(&mutex);
n = read(fd1, buf, sizeof(buf));
sleep(1);
if(n== 0)
{
perror("read");
break;
}
pthread_mutex_unlock(&mutex);
}
}
void *mywirte(void *arg)
{
fd2 = open( "./2.txt" , O_WRONLY|O_CREAT|O_TRUNC, 0666);
while(1)
{
write(fd2,buf,n);
sleep(1);
}
}
int main(int argc, const char *argv[])
{
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
pthread_t tid1,tid2;
pthread_create(&tid1, NULL, myread, NULL);
pthread_detach(tid1);
pthread_create(&tid2, NULL, mywirte, NULL);
pthread_detach(tid2);
pthread_join(tid2,NULL);
while(1);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
pthread_mutex_t mutex;
char buf[1024] = {0};
int m = 0;
void *mywirte(void *arg)
{
int fp2 = open("./2.txt",O_WRONLY|O_CREAT|O_TRUNC,0777);
while(1)
{
write(fp2,buf,m);
sleep(1);
}
}
void *myread(void *arg)
{
int fp1 = open("./1.txt",O_RDONLY|O_CREAT,0777);
while(1)
{
pthread_mutex_lock(&mutex);
m = read(fp1,buf,sizeof(buf));
sleep(1);
if(m == 0)
{
printf("readover\n");
break;
}
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,mywirte,NULL);
pthread_detach(tid1);
pthread_create(&tid2,NULL,myread,NULL);
pthread_detach(tid2);
pthread_mutex_init(&mutex,NULL);
//close(fp1);
//close(fp2);
while(1);
return 0;
}
七、进程间通信
传统进程间通信方式:
无名管道、有名管道、信号
system V的 IPC对象:
消息队列、共享内存、信号灯集(信号量)
BSD:
套接字
进程间通信(Inter-Process Communication,IPC)是操作系统中用于不同进程之间传递数据和同步状态的一种机制。进程间通信的方式主要有以下几种:
1. 管道(Pipe):管道是一种单向数据流通信机制,可以看作是进程间的单向数据通道。它允许一个进程向另一个进程发送数据,但不能反向通信。管道分为匿名管道和命名管道两种。
2. 命名管道(无名管道)(Named Pipe):命名管道也称为FIFO,它是一种特殊的文件,可以在任意两个进程之间建立通信通道。命名管道的通信效率比匿名管道要高,因为它避免了在内核中为每个管道创建数据结构。
3. 消息队列(Message Queue):消息队列是一种先进先出(FIFO)的数据结构,用于进程间的消息传递。发送进程将消息放入队列中,接收进程从队列中读取消息。消息队列可以实现多个进程之间的通信。
4. 信号(Signal):信号是一种简单的通信方式,用于通知接收进程某个事件已经发生。信号处理函数可以用来处理信号,从而实现进程间的异步通知。
5. 共享内存(Shared Memory):共享内存允许两个或多个进程访问同一块内存空间,从而实现数据共享。共享内存的通信效率最高,但需要进程间同步,以避免数据竞争和冲突。
6. 套接字(Socket):套接字是一种基于网络的通信机制,可以实现不同主机上的进程间通信,也可以用于同一主机上的进程间通信。套接字支持面向连接和无连接两种通信方式,以及可靠的传输。
7. 信号量(Semaphore):信号量是一种用于进程间同步的机制,可以保证多个进程访问共享资源的互斥和同步。信号量可以用于控制对共享资源的访问,以避免竞争和冲突。
1.无名管道(pipe)
查看命令: man 2 pipe
头文件:
#include <unistd.h>
函数原型:
int pipe(int pipefd[2]);
参数:
pipefd[2] :无名管道的两个文件描述符,int型的数组,大小为2,pipefd[0]为读端,pipefd[1]为写端
返回值:
成功:0
失败:-1
1.1 无名管道的特点
a、没有名字,因此无法使用open()打开
b、只能用于亲缘进程间(如父子进程、兄弟进程、祖孙进程等)通信
c、半双工工作方式,读写端是分开的,pipefd[0]为读端,pipefd[1]为写端
d、是一种特殊的文件,只存在内存中,由内核进行管理
e、对于它的读写可以使用文件IO如read、write函数
f、无名管道的操作属于一次性操作,如果对无名管道进行读操作,数据会被全部读走
单工:固定一种方向进行通信 广播
半双工:同一时间只能由一端发送到另一端 对讲机
全双工:通信方向都可以,同时可以发送也可以接收 电话
既然说是管道,所以可以想象成一条水管,连接两个进程, 一个进程负责输入数据,另一个进程负责接收数据,反过来也一样。
所以在无名管道中也一样,无名管道的两端,每一端都可以读和写。
调用fork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])。
1.2 示例:一个进程
1)fork进程代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(-1);
} else if (pid == 0) {
// 子进程写数据
int fd = open("./1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
write(fd, "hello\n", 7);
close(fd);
_exit(0); // 子进程结束后退出
} else {
int status;
waitpid(pid, &status, 0); // 等待子进程结束
sleep(1); // 等待子进程写入完成
// 父进程读数据
int fd = open("./1.txt", O_RDONLY);
char buf[100] = {0};
read(fd, buf, sizeof(buf));
printf("%s\n", buf);
close(fd);
}
return 0;
}
2)pipe进程代码
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe");
return -1;
}
write(fd[1],"hello world",12);
char buf[100] = {0};
int n =read(fd[0],buf,sizeof(buf));
printf("n = %d\n buf =%s\n",n,buf);
return 0;
}
1.3 示例:两个亲缘进程
若一端为读就要关闭他的写功能,另一端就只能为写关闭读功能。
1)亲缘进程代码
/*===============================================
* 描 述:亲缘进程
================================================*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd[2];//两个管道,fd[0]读端,fd[1]写端
int ret = pipe(fd);//创建无名管道
if(ret == -1)//判断管道读写是否成功
{
perror("pipe");
return -1;
}
pid_t pid = fork();//创建进程
if(pid < 0)//判断进程是否创建成功
{
perror("fork");
return -1;
}
else if(pid == 0)//子进程
{
close(fd[0]);//关闭读端
char buf[100] = {0};
while(1)
{
fgets(buf,sizeof(buf),stdin);//从键盘上输入内容
write(fd[1],buf,strlen(buf));//进行管道写
}
}
else
{
close(fd[1]);//关闭写
char buf[100] = {0};
while(1)
{
read(fd[0],buf,sizeof(buf));//进行读操作
printf("%s\n",buf);//打印相关信息
memset(buf,0,100);//对空间进行释放
}
}
return 0;
}
1.4 实现本地聊天软件
1)user1(用户1)
/*===============================================
* 描 述:本地聊天软件(user1)
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
char buf[100] = {0};
void *write_data(void *arg)
{
int fd = open("./f1", O_WRONLY);
while(1)
{
fgets(buf,sizeof(buf),stdin);
write(fd, buf, strlen(buf));
}
}
void *read_data(void *arg)
{
int fd = open("./f2", O_RDONLY);
while(1)
{
read(fd, buf, sizeof(buf));
printf("buf=%s\n", buf);
memset(buf, 0, 100);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,read_data,NULL);
pthread_detach(tid1);
pthread_create(&tid2,NULL,write_data,NULL);
pthread_detach(tid2);
while(1);
return 0;
}
2)user2(用户2)
/*===============================================
* 描 述:本地聊天软件(user2)
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
char buf[100] = {0};
void *write_data(void *arg)
{
int fd = open("./f2", O_WRONLY);
while(1)
{
fgets(buf,sizeof(buf),stdin);
write(fd, buf, strlen(buf));
}
}
void *read_data(void *arg)
{
int fd = open("./f1", O_RDONLY);
while(1)
{
read(fd, buf, sizeof(buf));
printf("buf=%s\n", buf);
memset(buf, 0, 100);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,read_data,NULL);
pthread_detach(tid1);
pthread_create(&tid2,NULL,write_data,NULL);
pthread_detach(tid2);
while(1);
return 0;
}
1.5 其他实现
1)当管道中无数据时,执行读数据,读操作堵塞
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd[2];
pipe(fd);
printf("11111111\n");
// write(fd[1],"hello\n",7);
char buf[100] = {0};
read(fd[0],buf,sizeof(buf));
printf("buf = %s\n",buf);
printf("222222222\n");
return 0;
}
2) 求管道大小
第一种
/*===============================================
* 描 述:管道大小
================================================*/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd[2];
pipe(fd);
char ch = 'x';
int i;
for(i = 0;i < 1024*64-1;i++)
{
write(fd[1],&ch,1);
}
printf("1111111111\n");
write(fd[1],&ch,1);
printf("2222222222\n");
return 0;
}
第二种
/*===============================================
* 描 述:求管道大小
================================================*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd[2];
pipe(fd);
char buf[4096] = {0};
for(int i=0;i<sizeof(buf);i++)
{
buf[i] = 'a';
}
for(int j = 0; ; j++)
{
write(fd[1],buf,sizeof(buf));
printf("%d\n",j);
}
close(fd[0]);
close(fd[1]);
return 0;
}
第三种
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>
int main()
{
int fd[2];
if(pipe(fd) < 0)
{
perror("pipe");
return -1;
}
int ret;
int size = 0;
int flags = fcntl(fd[1], F_GETFL);
fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); // 设置为非阻塞
while (1)
{
ret = write(fd[1], "c", 1);
if (ret < 0)
{
perror("write");
break;
}
size++;
}
printf("size=%d\n", size);
return 0;
}
3)pipe_queue(队列)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd[2];
pipe(fd);
write(fd[1], "xxx", 3);
write(fd[1], "yyy", 3);
write(fd[1], "hello", 5);
char buf[100] = {0};
read(fd[0], buf, 4);
printf("1 = %s\n", buf);
memset(buf, 0, 100);
read(fd[0], buf, 4);
printf("2 = %s\n", buf);
memset(buf, 0, 100);
return 0;
}
4)pipe_singal
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd[2];
pipe(fd);
close(fd[0]);
write(fd[1], "xx", 2);
printf("-------------\n");
return 0;
}
5) pipe_wnohang
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd[2];
pipe(fd);
write(fd[1], "hello", 5);
close(fd[1]);
char buf[100] ={0};
int n1 = read(fd[0], buf, 5);
printf("-------n1 = %d------\n", n1);
int n2 = read(fd[0], buf, 5);
printf("-------n2 = %d------\n", n2);
return 0;
}
1.6 无名管道需要注意的问题
当管道中无数据时,执行读操作,读操作阻塞
无名管道的大小是固定的,管道一旦满,写操作就会导致进程阻塞
对无名管道的操作,类似一个队列,后写入的数据不会覆盖之前的数据,会在其后面存储,读取完的数据会从管道里面移除
将读端关闭,向无名管道中写数据,管道破裂,进程收到信号(SIGPIPE),默认这个信号会将进程杀死
当管道中有数据,将写端关闭,读操作可以执行,之后数据读完,可以继续读取(非阻塞),直接返回0
2.有名管道(fifo)
查看命令:man 3 mkfifo
1、查看命令:man 3 mkfifo
2、头文件:
#include <sys/types.h>
#include <sys/stat.h>
3、函数原型:
int mkfifo(const char *pathname, mode_t mode);
功能: 创建有名管道
参数:
pathname: 文件的路径名
mode: 创建的权限(0644)
在shell中使用mkfifo命令: mkfifo filename
eg:
mkfifo f1
or
if(mkfifo("fifo",0666) == -1)
{
perror("mkfifo ");
return -1;
}
2.1 有名管道的特点
有名管道也叫命名管道,在文件系统目录中存在一个管道文件。
管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
特点:
1. 有名管道存在文件系统中,数据存在内存中
2. 可以用于无亲缘关系的进程
3. 只有读端和写端同时存在管道才能打开成功。
2.2 数据传输特点
1.读端不存在时,写端写入数据将会阻塞
2.读端意外结束,写端再写数据将会管道破裂,该进程结束
3.有名管道的数据存储在内存中,数据交互在内核中
2.3 示例:fifo_weite
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
//写
char buf[100] = {0};
int fp = open("./f1",O_WRONLY);
while(1)
{
fgets(buf,sizeof(buf),stdin);
write(fp,buf,strlen(buf));
}
close(fp);
return 0;
}
2.4 示例:fifo_read
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
//读
int fd = open("./f1", O_RDONLY);
char buf[100] = {0};
while(1)
{
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
memset(buf, 0, 100);
}
return 0;
}
2.5 综合(fifo)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
//写
int fd = open("./f1", O_WRONLY);
char buf[100] = {0};
while(1)
{
gets(buf);
write(fd, buf, strlen(buf));
}
}
else{
//读
int fd = open("./f1", O_RDONLY);
char buf[100] = {0};
while(1)
{
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
memset(buf, 0, 100);
}
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
char buf[100] = {0};
void *write_data(void *arg)
{
int fd = open("./f2", O_WRONLY);
while(1)
{
fgets(buf,sizeof(buf),stdin);
write(fd, buf, strlen(buf));
}
}
void *read_data(void *arg)
{
int fd = open("./f1", O_RDONLY);
while(1)
{
read(fd, buf, sizeof(buf));
printf("buf=%s\n", buf);
memset(buf, 0, 100);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,read_data,NULL);
pthread_detach(tid1);
pthread_create(&tid2,NULL,write_data,NULL);
pthread_detach(tid2);
while(1);
return 0;
}
2.6 有名管道和无名管道的异同点
1.相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
2.区别
有名在任意进程之间使用,无名在父子进程之间使用
3.信号
简单概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
kill把信号发送给进程或进程组;
raise把信号发送给(进程)自身
硬件产生:
SIGINT : ctrl + c 终止前台进程
SIGQUIT : ctrl +\ 终止进程
SIGTSTP : ctrl + z 停止进程
软件产生:
raise()函数: raise函数允许进程向自己发送信号
3.1 Kill()和raise()(信号发送)
#include <signal.h>
int kill(pid_t pid, int signo);
功能:
可以终止进程,也可以向进程发送其他信号
参数:
pid:
正数:发送信号给进程号为pid的进程
0:信号被发送到所有和当前进程在同一个进程组的进程
-1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
<-1:信号发送给进程组号为-pid的每一个进程
signo:信号类型
函数返回值:
成功:0
失败:-1
int raise(int signo);
功能:
只允许进程向自身发送信号
参数:
signo:信号类型
函数返回值:
成功:0
失败:-1
raise(signo); ====== kill(getpid(), signo);
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
// while(1);
sleep(5);
//杀死自己
//kill(getpid(),SIGKILL);
// raise(SIGKILL);
//杀死父进程
kill(getppid(),SIGKILL);
printf("---------------\n");
return 0;
}
3.2 alarm()和pause()(定时器信号)
1.alarm(设置闹钟)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
函数传入值:
seconds:指定秒数,系统经过seconds秒之后向该进程发送SIGALARM信号
函数返回值:
成功:如果调用此函数前,进程中已经设置了闹钟时间,
则返回上一个闹钟剩余时间,否则返回0
失败:-1
可以为当前进程定义闹钟,时间到了会发出SIGALRM信号。
每个进程只能有一个alarm,当重新定义时,会重新计时。
如果之前定义了一个闹钟,则这次定义返回的是上次闹钟剩余的时间,否则返回0.
1)示例代码
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
alarm(5); //定时5秒
sleep(2); //先运行2秒
int ret = alarm(5);//从上一个闹钟
printf("ret=%d\n",ret);
while(1);
return 0;
}
2)输出结果
2. pause(程序暂停)
int pause(void);
pause函数的作用,是让当前进程暂停运行,交出CPU给其他进程去执行;
当前进程进入pause状态后,当前进程会表现为“卡住、阻塞住”;
要退出pause状态,当前进程需要被信号唤醒。
1)示例代码
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
alarm(5); //定时5秒
sleep(2); //先运行2秒
pause(); //程序暂停
int ret = alarm(5);//从上一个闹钟
printf("ret=%d\n",ret);
while(1);
return 0;
}
2)输出结果
3.3 signal()和sigaction()(信号的设置)
信号的三种处理方式:
1.忽略 2.默认 3.自定义信号处理函数
1. signal(设置信号的处理方式)
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
捕获信号,设置信号的处理方式
参数:
signum:指定信号代码
handler: SIG_IGN:忽略
SIG_DFL:默认
自定义的信号处理函数
返回值:
成功:以前信号处理函数
失败:-1
1)示例代码
2. sigaction
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oidact);
参数:
signum:信号类型,除SIGKILL及SIGSTOP之外的任何一个信号
act:指向sigaction结构体的指针,包含对特定信号的处理
oidact:保存信号原先的处理方式
返回值:
成功:0
失败:-1
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
}
4.ipcs
ipcs -m :查询显示当前系统的共享内存
ipcs -q :查询显示当前系统的消息队列
ipcs -s :查询显示当前系统的信号灯集ipcrm -m shmid:删除某个共享内存
ipcrm -q msgid:删除某个消息队列
ipcrm -s semid:删除某个信号灯集
4.1 IPC步骤
key id
ftok ----> shm_get/msg_get/sem_get ---->
shmmat/shmdt/shmctrl
msgctrl/msgsend/msgrecv
semctrl/semop
4.2 ftok(生成一个key值)
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:生成一个KEY值
参数:
pathname: 路径名
proj_id: 1-255
返回值:
成功: key
失败: -1
5.共享内存(share memory )
5.1 共享内存的特点
1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2.为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
5.2 优缺点
优点:进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
缺点:由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
5.3 共享内存的相关操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
步骤:
1. ftok
2. shmget
3. shmat
4. 进程间通信 fork
5. shmdt
6. shmctl
int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
参数:
key: ftok函数的返回值 or IPC_PRIVATE
size: 内存大小 1024bytes 4096bytes
shmflg:
IPC_CREAT|0666
返回值:
成功:shmid
失败:-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射
参数:
shmid: shmget的返回值
shmaddr: NULL 自动完成映射
shmflg:
SHM_RDONLY:只读
0 :读写
返回值:
成功:内存地址
失败:NULL
int shmdt(const void *shmaddr);
功能:解除映射
参数:
shmaddr: shmat函数的返回值
返回值:
成功:0
失败:-1
int shmctl(int shmid, int cmd, struct shmid_ds
*buf);
功能: 控制函数
参数:
shmid: shmget函数的返回值
cmd: IPC_RMID 删除共享内存
buf: NULL
返回值:
成功:0
失败:-1
5.4 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
key_t key = ftok("/",1);
if(key == -1)
{
perror("ftok");
return -1;
}
int shmid = shmget(key,1024,IPC_CREAT|0666);
if(shmid == -1)
{
perror("shmget");
goto xxx;
}
printf("shmid:%d\n",shmid);
system("ipcs -m");//查看当前系统的共享内存
char *p = shmat(shmid,NULL,0);
if(p == NULL)
{
perror("shmat");
goto xxx;
}
//fork
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
goto xxx;
}
else if(pid == 0)
{
//子进程 写操作
char buf[64] = {0};
while(1)
{
fgets(buf,sizeof(buf),stdin);
strcpy(p,buf);//把buf拷贝到映射中
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
}
else
{
//父进程 读操作
waitpid(-1,NULL,WNOHANG);
while(1)
{
printf("p =%s\n",p);
sleep(1);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
}
int ret = shmdt(p);
if(ret == -1)
{
perror("shmdt");
goto xxx;
}
ret = shmctl(shmid,IPC_RMID,NULL);
if(ret ==-1)
{
perror("shmctl");
goto xxx;
}
xxx:
sleep(2); //防止出错
char str[100] = {0};
sprintf(str,"ipcrm -m %d",shmid);
system(str);
system("ipcs -m");
return 0;
}
5.5 共享内存read/write
/*read*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
key_t key = ftok("/",1);
if(key == -1)
{
perror("ftok");
return -1;
}
int shmid = shmget(key,1024,IPC_CREAT|0666);
if(shmid == -1)
{
perror("shmget");
goto xxx;
}
printf("shmid:%d\n",shmid);
system("ipcs -m");
char *p = shmat(shmid,NULL,0);
if(p == NULL)
{
perror("shmat");
goto xxx;
}
waitpid(-1,NULL,WNOHANG);
while(1)
{
printf("p =%s\n",p);
sleep(1);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
int ret = shmdt(p);
if(ret == -1)
{
perror("shmdt");
goto xxx;
}
ret = shmctl(shmid,IPC_RMID,NULL);
if(ret ==-1)
{
perror("shmctl");
goto xxx;
}
xxx:
sleep(2);
char str[100] = {0};
sprintf(str,"ipcrm -m %d",shmid);
system(str);
system("ipcs -m");
return 0;
}
/*write*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
key_t key = ftok("/",1);
if(key == -1)
{
perror("ftok");
return -1;
}
int shmid = shmget(key,1024,IPC_CREAT|0666);
if(shmid == -1)
{
perror("shmget");
goto xxx;
}
printf("shmid:%d\n",shmid);
system("ipcs -m");
char *p = shmat(shmid,NULL,0);
if(p == NULL)
{
perror("shmat");
goto xxx;
}
char buf[64] = {0};
while(1)
{
fgets(buf,sizeof(buf),stdin);
strcpy(p,buf);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
int ret = shmdt(p);
if(ret == -1)
{
perror("shmdt");
goto xxx;
}
ret = shmctl(shmid,IPC_RMID,NULL);
if(ret ==-1)
{
perror("shmctl");
goto xxx;
}
xxx:
sleep(2);
char str[100] = {0};
sprintf(str,"ipcrm -m %d",shmid);
system(str);
system("ipcs -m");
return 0;
}
6. 消息队列
6.1 消息队列的特点
1.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
2.消息队列可以按照类型来发送/接收消息,相同的类型满足先进先出,不同的类型可以随意存取.
3.全双工通信方式
6.2 消息队列的相关操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
步骤:
1. ftok
2. msgget
3. 进程间通信
4. msgsnd
5. msgrcv
6. msgctl
int msgget(key_t key, int msgflg);
功能:创建消息队列
参数:
key: ftok函数的返回值 or IPC_PRIVATE
msgflg: IPC_CREAT|0666
返回值:
成功:msgid
失败:-1
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[N]; /* message data */
};
struct msgbuf msg;
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
功能:发送数据
参数:
msgid: msgget函数的返回值
msgp: &msg
msgsz: 发送正文的长度
sizeof(msg) - sizeof(long)
msgflg:
IPC_NOWAIT:非阻塞
0:阻塞
返回值:
成功:0
失败:-1
struct msgbuf msg;
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收数据
参数:
msgid: msgget函数的返回值
msgp: &msg
msgsz:发送正文的长度
sizeof(msg) - sizeof(long)
msgtyp: 消息的类型
msgflg:
IPC_NOWAIT:非阻塞
0:阻塞
返回值:
成功:接收消息的长度
失败:-1
int msgctl(int msgid, int cmd, struct msqid_ds *buf);
功能: 删除消息队列
参数:
msgid: msgget函数的返回值
cmd: IPC_RMID
buf: NULL
返回值:
成功;0
失败:-1
6.3 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <unistd.h>
#define N 100
struct msgbuf{
long mtype;
char mtext[N];
};
int main(int argc, char *argv[])
{
key_t key = ftok("/",100);
if(key == -1)
{
perror("ftok");
return -1;
}
int msgid = msgget(key,IPC_CREAT|0666);
if(msgid == -1)
{
perror("msgget");
goto xxx;
}
printf("msgid:%d\n",msgid);
system("ipcs -q");
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
goto xxx;
}
else if( pid == 0)
{
struct msgbuf msg;
char buf[64] = {0};
while(1)
{
fgets(buf,sizeof(buf),stdin);
msg.mtype = 10;
strcpy(msg.mtext,buf);
msgsnd(msgid,&msg,sizeof(msg)-sizeof(long),0);
if(strncmp(buf,"quit",4)==0)
{
break;
}
}
}
else
{
waitpid(-1,NULL,WNOHANG);
struct msgbuf msg;
while(1)
{
msgrcv(msgid,&msg,sizeof(msg) - sizeof(long),10,0);
printf("msg:%s\n",msg.mtext);
if(strncmp(msg.mtext,"quit",4)==0)
{
break;
}
}
}
int ret = msgctl(msgid,IPC_RMID,NULL);
if(ret ==-1)
{
perror("msgctl");
goto xxx;
}
xxx:
sleep(2);
char str[100] = {0};
sprintf(str,"ipcrm -q %d",msgid);
system(str);
system("ipcs -q");
return 0;
}
7.信号量
信号量一般和共享内存配合使用
7.1 无名信号量 :适用于线程同步
sem_t
sem_init(); 初始化信号量
sem_wait(); P操作 -1
sem_post(); V操作 +1
sem_getvalue() 获得信号的值
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
sem_t sem; //信号量
void *fun(void *arg)
{
int n = 5;
while(n--)
{
printf("xxxx\n");
sleep(1);
}
sem_post(&sem); //+1
}
int main(int argc, const char *argv[])
{
//信号量的初始化
sem_init(&sem, 0, 0);
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
pthread_detach(tid);
printf("11111\n");
sem_wait(&sem); //-1
printf("22222\n");
while(1);
return 0;
}
7.2 有名信号量:适用于进程同步
P操作 sem_wait() -1
V操作 sem_post() +1
关闭有名信号量 sem_close()
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
char buf[100] = {0};
sem_t sem; //信号量
void *fun(void *arg)
{
while(1)
{
sem_wait(&sem); //-1
printf("%d\n", strlen(buf));
}
}
int main(int argc, const char *argv[])
{
//信号量的初始化
sem_init(&sem, 0, 0);
pthread_t tid;
pthread_create(&tid, NULL, fun, NULL);
pthread_detach(tid);
// sem_wait(&sem); //-1
while(1)
{
gets(buf);
sem_post(&sem); //+1
}
return 0;
}
7.3 信号灯集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
步骤:
1. ftok
2. shmget
3. shmat
* 4. semget
* 5. semctl
6. 进程间通信
* 7. semop
* 8. semctl
9. shmdt
10. shmctl
int semget(key_t key, int nsems, int semflg);
功能:创建信号灯集
参数:
key:ftok的返回值 or IPC_PRIVATE
nsems: 设置的信号灯的数目
semflg:
IPC_CREAT|0666
返回值:
成功:semid
失败:-1
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int semctl(int semid, int semnum, int cmd, ...); 功能:信号灯集控制函数
参数:
semid: semget的返回值
semnum: 要设置的信号灯的编号
cmd:
IPC_RMID: 删除信号灯集
SETVAL: 设置信号灯
...: 共用体的变量 or 不写
返回值:
成功:0
失败:-1
编号 初值
0 sem0 0
1 sem1 1
子进程:写入
sem1:-1
//write
sem0:+1
父进程:读取
sem0:-1
//read
sem1:+1
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号灯进行PV操作 -1 +1
参数:
semid: semget的返回值
sops: 结构体的变量的地址
struct sembuf{
unsigned short sem_num; /* semaphore number 信号灯的编号 */
short sem_op; /* semaphore operation 信号灯操作-1 +1*/
short sem_flg; /* operation flags 0:阻塞 */
}
nsops: 信号灯的个数 默认就是1
返回值:
成功:0
失败:-1
1)示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
/*
struct sembuf{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
*/
int main(int argc, const char *argv[])
{
key_t key = ftok("/", 1);
if(key == -1)
{
perror("ftok");
return -1;
}
int shmid = shmget(key, 1024, IPC_CREAT|0666);
if(shmid == -1)
{
perror("shmget");
goto xxx;
}
printf("shmid:%d\n",shmid);
system("ipcs -m");
char *p = shmat(shmid, NULL, 0);
if(p == NULL)
{
perror("shmat");
goto xxx;
}
//加入信号灯集来控制共享内存
int semid = semget(key, 2, IPC_CREAT|0666);
if(semid == -1)
{
perror("semget");
goto xxx;
}
//设置初值
union semun sem0 = {0};
union semun sem1 = {1};
//设置编号
semctl(semid, 0, SETVAL, sem0);
semctl(semid, 1, SETVAL, sem1);
//fork
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
goto xxx;
}
else if(pid == 0)
{
//子进程 写操作
char buf[64] = {0};
struct sembuf sem0;
sem0.sem_num = 1; //编号
sem0.sem_op = -1; //PV操作 +1 -1
sem0.sem_flg = 0;
struct sembuf sem1;
sem1.sem_num = 0;
sem1.sem_op = 1;
sem1.sem_flg = 0;
while(1)
{
semop(semid, &sem0, 1);
fgets(buf,sizeof(buf),stdin);
strcpy(p, buf);
if(strncmp(buf, "quit", 4) == 0)
{
break;
}
semop(semid, &sem1, 1);
}
}
else{
//父进程 读操作
waitpid(-1, NULL, WNOHANG);
struct sembuf sem0;
sem0.sem_num = 0;
sem0.sem_op = -1;
sem0.sem_flg = 0;
struct sembuf sem1;
sem1.sem_num = 1;
sem1.sem_op = 1;
sem1.sem_flg = 0;
while(1)
{
semop(semid, &sem0, 1);
printf("p=%s\n", p);
sleep(1);
if(strncmp(p, "quit", 4) == 0)
{
break;
}
semop(semid, &sem1, 1);
}
}
int ret = semctl(semid, 0|1, IPC_RMID);
if(ret == -1)
{
perror("semctl");
goto xxx;
}
ret = shmdt(p);
if(ret == -1)
{
perror("shmdt");
goto xxx;
}
ret = shmctl(shmid, IPC_RMID, NULL);
if(ret == -1)
{
perror("shmctl");
goto xxx;
}
xxx:
sleep(2); //防止出错
char str[100] = {0};
sprintf(str, "ipcrm -m %d", shmid);
system(str);
system("ipcs -m");
return 0;
}
8.套接字(socket)
见网络编程知识总结篇(即下一篇嵌入式系列学习)
八、往期回顾
2. C语言基础练习题总结(嵌入式系列学习2)-CSDN博客
3. Linux基础、C语言高级基础知识总结(嵌入式系列学习3)-CSDN博客
4. 数据结构与算法知识总结(嵌入式系列学习4)-CSDN博客
持续更新中.......
更多文章,点击左上角头像,查看个人简介和更多相关项目的分享,也可通过关注我的其他平台,一起学习,一起进步!Come on!!!动起手来,开始探索~~~