IO进程
scanf\printf:终端
IO:input/output,文件
标准IO
文件IO
文件属性获取:ls -l 文件类型 文件权限 链接数 用户名 组名 大小 时间 文件名
目录操作:ls
库
进程
进程:创建进程
线程:创建线程、同步和互斥
进程间通信:7->6种
一、标准IO
文件:7种文件类型
b(块设备) c(字符设备) d(目录) -(普通文件) l(链接文件) s(套接字) p(有名管道)
1、概念
1.1 定义
在C库中定义的一组专门用于输入输出的函数
1.2 特点
1)有缓冲机制,通过缓冲机制减少系统调用的次数,提高效率
系统调用:内核向上提供的一组接口
2)围绕流进行操作,流用FILE *描述,FILE是一个结构体,描述的是文件的相关信息
typedef struct _IO_FILE FILE;
3)默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准错误)
struct _IO_FILE *stdin; --> FILE *stdin;
补充:ctags的使用(可以追代码)
vi -t FILE(typedef定义数据类型、宏定义、结构体等)
选择合适的编号
将光标定位在目标位置,ctrl+] :向下追代码
ctrl+t:回退
q:退出
1.3 缓存区
1)全缓存:和文件相关
刷新缓存区的条件:
1-程序正常结束
2-缓存区满刷新
3-fflush强制刷新
2)行缓存:和终端相关
刷新缓存区的条件:
1-程序正常结束
2-\n刷新缓存
3-缓存区满刷新
4-fflush强制刷新
3)不缓存:没有缓存区,标准错误
练习:计算行缓存中标准输出的缓存区大小
#include <stdio.h>
int main(int argc, char const *argv[])
{
// for(int i = 0; i < 300; i++)
// printf("%4d", i);
// while(1);
// 结构体,stdout _IO_buf_end
printf("hello");
printf("%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);
return 0;
}
2.函数接口
2.1 打开文件
FILE *fopen(const char *path, const char *mode);
参数:
path:打开文件
mode:打开方式
r:只读,流被定位到文件开头
r+:可读可写,流被定位到文件开头
w:只写,文件不存在创建,文件存在清空,流被定位到文件开头
w+:可读可写,文件不存在创建,文件存在清空,流被定位到文件开头
a:追加,文件不存在创建,存在追加,流被定位到文件末尾
a+:可读可写,文件不存在创建,存在追加,开始进行读从头读,进行写流被定位到文件末尾
返回值:成功:文件流
失败:NULL,并且设置errno(错误码)
2.2 读写文件
2.2.1 每次一个字符的读写
int fgetc(FILE *stream);
功能:从文件中读一个字符
参数:stream:文件流
返回值:成功:读到字符的ASCII
失败或读到文件末尾:EOF
int fputc(int c, FILE * stream)
功能:向文件中写入一个字符
参数:c:要写的字符
stream:文件流
返回值:成功:写的字符的ASCII
失败:EOF
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *fp;
fp=fopen("./i2.c","w");
if(fp==NULL)
{
perror("fopen err");
return -1;
}
fputc('a',fp); //存放字符到指定文件中
fputc(32,fp);
fputc('a',stdout); //向终端(标准输出)一个函数
return 0;
}
练习:编程实现cat功能
思路:打开文件,循环读文件(fgetc),当读到文件末尾(fgetc函数返回值为EOF)循环结束,打印读到的内容
#include <stdio.h>
int main(int argc, const char *argv[])
{
FILE *p; //定义结构体指针
int ch;
if (argc!=2) //传入的参数不等于2个的时候进行提示
{
printf("usage:%s <filename>\n",argv[0]);
return -1;
}
p=fopen(argv[1],"r"); //打开文件
if (p==NULL)
{
perror("open err");
//printf("open error\n");
return -1;
}
while((ch=fgetc(p))!=EOF) //循环打印出文件的全部内容
{
printf("%c",ch);
}
return 0;
}
补充:
int feof(FILE * stream);
功能:判断文件有没有到结尾
返回:到达文件末尾,返回非零值
int ferror(FILE * stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值
void perror(const char *s);
功能:根据errno打印错误信息
参数:s:要打印的字符串
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp;
fp = fopen("./a.c", "w");
if(fp == NULL)
{
// printf("fopen err\n");
perror("fopen err");
return -1;
}
printf("fopen success\n");
int ch = fgetc(fp);
printf("%c\n", ch);
//fgetc返回值为EOF时,是因为读到末尾还是因为调用失败,可以用这两个函数区分
if(feof(fp)) //判断是否读到文件末尾
printf("eof\n");
if(ferror(fp)) //判断函数是否调用失败
printf("error\n");
return 0;
}
补充vscode使用:
- 将work目录下的.vscode文件夹复制到自己目录(file)下
- 切换到file的上一级目录
- code file,用vscode打开目录,编写代码即可
- 快捷方式:
1)ctrl+shift+i:代码自动对齐
2)ctrl+/:注释代码
3)代码追踪:
ctrl+鼠标左键:向下追
alt+键盘左健:回退
2.2.2 每次一行的读写
char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取一串字符
参数:s:存放读取的字符串的首地址
size:读取的大小
stream:文件流
返回值:成功:读取的字符串的首地址
失败或读到文件末尾:NULL
特性:1.实际读取size-1个字符,在末尾添加\0
2.读到\n结束读取
int fputs(const char *s, FILE *stream);
功能:向文件中写字符串
参数:s:要写的内容
stream:文件流
返回值:成功:非负整数
失败:EOF
练习:编程实现计算一个文件行数的功能(wc -l 文件名)。
要求:使用fgets实现
思路:打开文件,循环读文件,当读到文件末尾(fgets返回值为NULL)循环结束,在循环中判断字符串中是否有\n,如果是\n,则变量n++即可
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
FILE *fp;
int n=0;
char buf[32]="";
fp=fopen("./i2.c","r");
if(fp==NULL)
{
perror("fopen err");
return -1;
}
while(fgets(buf,30,fp)!=NULL)
{
#if 0
for(int i=0;buf[i]!='\0';i++)
{
if(buf[i]=='\n')
n++;
}
#endif
if(buf[strlen(buf)-1]=='\n')
n++;
}
printf("%d\n",n);
return 0;
}
2.3 关闭文件
int fclose(FILE* stream);
功能:关闭文件
参数:stream:文件流
练习一:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据
类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
sleep(1);
fprintf/sprintf();
time(); //计算时间,秒
localtime(); //秒转换成年月日时分秒
思路:打开文件,计算行数,循环向文件中写字符串,每隔一秒写入一行
注意:全缓存
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
FILE *fp;
char buf[32] = "";
int n = 0;
fp = fopen("test.txt", "a+");
if(fp == NULL)
{
perror("fopen err");
return -1;
}
//判断行数
while(fgets(buf, 32, fp) != NULL)
{
if(buf[strlen(buf)-1] == '\n')
n++;
}
time_t tm;
struct tm *t;
while(1)
{
//计算时间
// time(&tm);
tm = time(NULL);
t = localtime(&tm);
fprintf(fp, "%d,%d-%d-%d %d-%d-%d\n", ++n,t->tm_year+1900,t->tm_mon+1,\
t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
fflush(NULL);
sleep(1);
}
return 0;
}
练习二:实现head功能
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
FILE *fp;
char buf[32] = "";
int n = 0;
int num = atoi(argv[1]+1); //argv[1]:"-15"
fp = fopen(argv[2], "r");
if(fp == NULL)
{
perror("fopen err");
return -1;
}
while(fgets(buf, 32, fp) != NULL)
{
if(buf[strlen(buf)-1] == '\n')
n++;
printf("%s", buf);
if(n == num)
break;
}
return 0;
}
2.4 二进制读写
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件流读取多个元素
参数: ptr :用来存放读取元素
size :元素大小 sizeof(数据类型)
nmemb :读取对象的个数
stream :要读取的文件
返回值:成功:读取对象的个数
读到文件尾或失败:0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:按对象写
参数:同上
返回值:成功:写的元素个数
失败 :-1
Fread和fwrite函数注意:
1)两个函数的返回值为:读或写的对象数
2)对于二进制数据我们更愿意一次读或写整个结构。
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp;
int arr[3] = {10, 20, 30}, num[3] = {0};
fp = fopen("a.c", "w+");
if(fp == NULL)
{
perror("fopen err");
return -1;
}
fwrite(arr, sizeof(int), 3, fp);
//将文件位置移动到文件开头
rewind(fp);
fread(num, sizeof(int), 3, fp);
for(int i = 0; i < 3; i++)
printf("%d\n", num[i]);
return 0;
}
2.5 文件定位操作
void rewind(FILE *stream);
功能:将文件位置指针定位到起始位置
int fseek(FILE *stream, long offset, int whence);
功能:文件的定位操作
参数:stream:文件流
offset:偏移量:正数表示向后文件尾部偏移,负数表示向文件开头偏移
whence:相对位置:
SEEK_SET:相对于文件开头
SEEK_CUR:相对于文件当前位置
SEEK_END:相对于文件末尾
返回值:成功:0
失败:-1
注:当打开文件的方式为a或a+时,fseek不起作用
long ftell(FILE *stream);
功能:获取当前的文件位置
参数:要检测的文件流
返回值:成功:当前的文件位置,出错:-1
#include <stdio.h>
int main(int argc, char const *argv[])
{
FILE *fp;
char buf[32] = "";
int n = 0;
fp = fopen("test.txt", "r+");
if(fp == NULL)
{
perror("fopen err");
return -1;
}
//将文件位置进行定位操作
fseek(fp, 10, SEEK_SET); //相对文件开头向后偏移
fputc('a', fp);
fseek(fp, -5, SEEK_CUR); //相对文件当前向前偏移
fputc('b', fp);
long l = ftell(fp); //获取当前文件位置
printf("%ld\n", l);
//计算文件长度
// fseek(fp, 0, SEEK_END);
// l = ftell(fp);
//rewind和fseek等价
// rewind(fp); //fseek(fp, 0, SEEK_SET);
return 0;
}
2.6 重定向打开文件
FILE * freopen(const char *pathname, const char *mode, FILE* fp)
功能:将指定的文件流重定向到打开的文件中
参数:path:文件路径
mode:打开文件的方式(同fopen)
fp:文件流指针
返回值:成功:返回文件流指针
失败:NULL
#include <stdio.h>
int main(int argc, char const *argv[])
{
printf("hello\n");
//将标准输出重定向到打开的文件
freopen("test.txt", "r+", stdout);
printf("world\n");
//将标准输出重定向到终端
freopen("/dev/tty", "r+", stdout);
printf("nihao\n");
return 0;
}
练习:通过标准IO实现cp功能
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
FILE *fd;
FILE *fd1;
char ch;
fd=fopen(argv[1],"r");
fd1=fopen(argv[2],"w+");
if(fd==NULL)
{
perror("open eror\n");
return -1;
}
if(fd1==NULL)
{
perror("open eror\n");
return -1;
}
while((ch=fgetc(fd))!=EOF)
fputc(ch,fd1);
fclose(fd);
fclose(fd1);
return 0;
}
百度+man手册
time(time_t *tm)
函数调用:
参数:个数、类型和函数原型意义对应;当函数原型中参数是一级指针时,需要定义变量传地址
返回值:并不是所有函数都需要接收返回值;如果需要接收返回值,函数原型返回值类型是什么,在代码定义什么类型变量或指针去接收
二、文件IO
1、概念
1.1 定义
在posix(可移植操作系统接口)中定义的一组输入输出的函数
系统调用:内核向上提供的一组接口
1.2 特点
1)没有缓冲机制,每次IO操作都会引起系统调用
2)围绕文件描述符操作,非负整数(int),依次分配
3)默认打开三个文件描述符:0(标准输入)、1(标准输出)、2(标准错误)
4)可以操作除d以外其他任意类型文件
2、函数接口
2.1 打开文件
int open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
flags:打开文件的方式
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:创建
O_TRUNC:清空
O_APPEND:追加
返回值:成功:文件描述符
失败:-1
当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限
int open(const char *pathname, int flags, mode_t mode);
创建出来的文件权限为指定权限值&(~umask) //umask为文件权限掩码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd;
char buf[32] = "";
// fd = open("./a.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
fd = open("test.txt", O_RDWR);
if (fd < 0)
{
perror("open err");
return -1;
}
printf("fd:%d\n", fd);
//read返回值s表示实际读到的字符个数
ssize_t s = read(fd, buf, 32);
printf("%s\n", buf);
printf("%d\n", s);
write(fd, "nihao", 5);
close(fd);
return 0;
}
比较:打开文件方式标准IO和文件IO对应关系
标准IO | 文件IO |
r | O_RDONLY |
r+ | O_RDWR |
w | O_WRONLY|O_CREAT|O_TRUNC,0666 |
w+ | O_RDWR|O_CREAT|O_TRUNC,0666 |
a | O_WRONLY|O_CREAT|O_APPEND,0666 |
a+ | O_RDWR|O_CREAT|O_APPEND,0666 |
2.2 读写文件
ssize_t read(int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数:fd 文件描述符
buf 存放位置
count 期望的个数
返回值:成功:实际读到的个数
返回-1:表示出错,并设置errno号
返回0:表示读到文件结尾
ssize_t write(int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd 文件描述符
buf 要写的内容
count 期望值
返回值:成功:实际写入数据的个数
失败 : -1
练习:实现cp功能。
cp srcfile newfile -> ./a.out srcfile newfile
思路:打开两个文件,循环读源文件写新文件,当读到源文件末尾时循环结束
diff 文件名1 文件名2:比较两个文件是否相等
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd_src, fd_new;
char buf[32] = "";
ssize_t s;
fd_src = open(argv[1], O_RDONLY);
fd_new = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666);
if(fd_src < 0 || fd_new < 0)
{
perror("open err");
return -1;
}
while(1)
{
s = read(fd_src, buf, 32);
if(s == 0)
break;
write(fd_new, buf, s);
}
close(fd_src);
close(fd_new);
return 0;
}
2.3 关闭文件
int close(int fd);
参数:fd:文件描述符
2.4 文件定位
off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
offset偏移量
正数:向文件结尾位置移动
负数:向文件开始位置
whence 相对位置
SEEK_SET 开始位置
SEEK_CUR 当前位置
SEEK_END 结尾位置
返回值:成功:文件的当前位置
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd;
char buf[32] = "";
fd = open("test.txt", O_RDWR);
if (fd < 0)
{
perror("open err");
return -1;
}
printf("fd:%d\n", fd);
lseek(fd, 10, SEEK_SET);
write(fd, "a", 1);
off_t off = lseek(fd, 0, SEEK_CUR);
printf("%ld\n", off);
close(fd);
return 0;
}
练习二:实现如下功能
1-- 打开一个文件,不存在创建,存在清零
2-- 向文件中第 10 位置处写一个字符,
3-- 在文件此时的位置,后 20个位置处,写一行字符串hello进去
4-- 求文件的长度。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[32]="";
fd=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open eror\n");
return -1;
}
lseek(fd,9,SEEK_SET);
write(fd,"i",1);
lseek(fd,19,SEEK_CUR);
write(fd,"hello",5);
off_t off=lseek(fd,0,SEEK_END);
printf("%ld\n",off);
close(fd);
return 0;
}
2.5 标准IO与文件IO的比较
标准IO | 文件IO | |
定义 | 在C库中定义输入输出的函数 | 在posix中定义的输入输出的函数 |
特点 | 有缓冲机制 围绕流操作,FILE* 默认打开三个流:stdin/stdout/stderr 只能操作普通文件 | 没有缓冲机制 围绕文件描述符操作,int非负整数 默认打开三个文件描述符:0/1/2 除d外其他任意类型文件 |
函数接口 | 打开文件:fopen/freopen 读写文件:fgetc/fputc、fgets/fputs、fread/fwrite 关闭文件:fclose 文件定位:rewind、fseek、ftell | 打开文件:open 读写文件:read、write 关闭文件:close 文件定位:lseek |
2.6 文件属性获取
int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:path:文件路径名
buf:保存文件属性信息的结构体
返回值:成功:0
失败:-1
struct stat {
ino_t st_ino; /* inode号 */
mode_t st_mode; /* 文件类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 用户ID */
gid_t st_gid; /* 组ID */
off_t st_size; /* 大小 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态改变时间 */
};
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
struct stat st;
if (stat("./test.c", &st) < 0)
{
perror("stat err");
return -1;
}
printf("%lu\n", st.st_ino);
printf("%d\n", st.st_nlink);
//判断文件类型
printf("%#o\n", st.st_mode);
if((st.st_mode & S_IFMT) == S_IFREG)
putchar('-');
else if((st.st_mode & S_IFMT) == S_IFDIR)
putchar('d');
//判断文件权限
if(st.st_mode & S_IRUSR)
putchar('r');
else
putchar('-');
if(st.st_mode & S_IWUSR)
putchar('w');
else
putchar('-');
//获取用户名和组名
//getpwuid();//将用户ID转换成用户名
//getgrgid();//将组ID转换成组名
//时间
//st_mtime:最后一次修改时间,s
//localtime();
return 0;
}
2.7 目录操作
围绕目录流进行操作,DIR *
DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
失败:NULL
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息
失败或读到目录结尾:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
ino_t d_ino; /* 索引节点号*/
off_t d_off; /*在目录文件中的偏移*/
unsigned short d_reclen; /* 文件名长度*/
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名 */
};
int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
DIR *dir;
struct dirent *d;
dir = opendir(".");
if (dir == NULL)
{
perror("opendir err");
return -1;
}
while((d = readdir(dir)) != NULL)
{
if(d->d_name[0] != '.')
printf("%s\n", d->d_name);
}
// d = readdir(dir);
// printf("%s\n", d->d_name);
// d = readdir(dir);
// printf("%s\n", d->d_name);
closedir(dir);
return 0;
}
练习:编程实现ls功能
三、库
1、库的定义
当使用别人的函数时除了包含头文件以外还要有库
库:就是把一些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用;本质上来说库是一种可执行代码的二进制形式
由于windows和linux的本质不同,因此二者库的二进制是不兼容的
2、库的分类
静态库和动态库,本质区别是代码被载入时刻不同。
1) 静态库在程序编译时会被连接到目标代码中。
优点:程序运行时将不再需要该静态库;运行时无需加载库,运行速度更快
缺点:静态库中的代码复制到了程序中,因此体积较大;
静态库升级后,程序需要重新编译链接
2) 动态库是在程序运行时才被载入代码中。
优点:程序在执行时加载动态库,代码体积小;
程序升级更简单;
不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差
3、库的制作
3.1 静态库的制作
1-将源文件编译生成目标文件
gcc -c add.c -o add.o
2-创建静态库用ar命令,它将很多.o转换成.a
ar crs libmyadd.a add.o
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
3-测试使用静态库:
gcc main.c -L. -lmyadd // -L指定库的路径 -l指定库名
执行./a.out
3.2 动态库的制作
1-我们用gcc来创建共享库
gcc -fPIC -c hello.c -o hello.o
-fPIC 创建与地址无关的编译程序
gcc -shared -o libmyhello.so hello.o
2-测试动态库使用
gcc main.c -L. -lmyhello
可以正常编译通过,但是运行时报错./a.out: error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件
解决方法(有三种):
(1)把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(终端关闭,环境变量就没在了)
(3) 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/hq/teach/22092/day3/dynamic
补充:
头文件:
放在当前目录:#include "xx.h",从当前路径下查找文件,如果没有再从系统目录下查找
放在系统目录:#include ,默认从系统路径下查找,系统路径:/usr/include
放在其他目录:#include "xx.h",在用gcc编译代码时加选项-I(i的大写)指定头文件的路径
gcc main.c -I头文件路径
库文件:动态库放在系统目录
系统路径:/usr/lib 和 /lib
gcc 编译时需要添加选项
-L 路径:指定库的路径
-l库名:(小写的L)指定库名
-I 路径:(大写的i)指定头文件的路径
四、进程
1、概念:
1.1 程序和进程区别:
程序:编译好的可执行文件
存放在磁盘上的指令和数据的有序集合(文件)
程序是静态的,没有任何执行的概念
进程:一个独立的可调度的任务
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
1.2 特点
- 系统会为每个进程分配0-4g的虚拟空间,其中0-3g是用户空间,每个进程独有;3g-4g是内核空间,所有进程共享
- 轮转调度:时间片,系统为每个进程分配时间片(几毫秒~几十毫秒),当一个进程时间片用完时,CPU调度另一个进程,从而实现进程调度的切换
1.3. 进程段:
Linux中的进程包含三个段:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
1.4. 进程分类:
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束
1.5. 进程状态:
1)运行态(TASK_RUNNING):R
指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。
2)睡眠态(等待态):
可中断睡眠态(TASK_INTERRUPTIBLE)S:处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:该状态的进程只能用wake_up()函数唤醒。
3)暂停态(TASK_STOPPED):T
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
4)死亡态:进程结束 X
5)僵尸态:Z 当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
< 高优先级
N 低优先级
s 会话组组长
l 多线程
+ 前台进程
1.6. 进程状态切换图
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。
2、函数:
2.1 创建进程
pid_t fork(void);
功能:创建子进程
返回值:
成功:在父进程中:返回子进程的进程号 >0
在子进程中:返回值为0
失败:-1并设置errno
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int num = 10;
pid_t id;
id = fork(); //创建子进程
if(id < 0)
{
perror("fork err");
return -1;
}
else if(id == 0)
{
//in the child
printf("in the child\n");
}
else
{
// int s;
// wait(&s); //回收子进程资源,阻塞函数
// printf("%d\n", s);
wait(NULL);
//in the parent
printf("in the parent\n");
while(1);
}
// while(1);
return 2;
}
特点:
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
4)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)
2.2 回收进程资源
pid_t wait(int *status);
功能:回收子进程资源,阻塞函数,等待子进程退出后结束阻塞
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
失败:-1
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
pid:>0 指定子进程进程号
=-1 任意子进程
=0 等待其组ID等于调用进程的组ID的任一子进程
<-1 等待其组ID等于pid的绝对值的任一子进程
status:子进程退出状态
options:0:阻塞
WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
当使用选项WNOHANG且没有子进程结束时:0
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t id;
id = fork(); //创建子进程
if (id < 0)
{
perror("fork err");
return -1;
}
else if (id == 0)
{
sleep(1);
//in the child
printf("in the child\n");
}
else
{
//IO模型:阻塞IO\非阻塞IO
// waitpid(-1, NULL, 0); //wait(NULL);
//轮询(循环)
while(1)
{
if(waitpid(id, NULL, WNOHANG)!=0) //WNOHANG:表示非阻塞
break;
}
//in the parent
printf("in the parent\n");
while (1)
;
}
return 0;
}
2.3 结束进程
void exit(int status);
功能:结束进程,刷新缓存
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
exit和return区别:
exit:不管在子函数还是主函数,都可以结束进程
return:当子函数中有return时返回到函数调用位置,并不结束进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int fun()
{
printf("in fun\n");
exit(0);
// return 0;
}
int main(int argc, char const *argv[])
{
printf("hello\n");
// fun();
// printf("world\n");
// return 0;
// exit(0); //结束进程,刷新缓存
// _exit(0); //结束进程,不刷新缓存区
// execl("/bin/ls", "ls", "-l", NULL);
// char *arg[] = {"ls", "-l", NULL};
// execv("/bin/ls", arg);
while(1);
return 0;
}
2.4 获取进程号
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t id;
if ((id = fork()) < 0)
{
perror("fork err");
return -1;
}
else if (id == 0)
{
printf("in child, pid:%d ppid:%d\n", getpid(), getppid());
}
else
{
printf("in parent, pid:%d ppid:%d\n", id, getpid());
}
return 0;
}
exec函数-了解
system("ls -l");
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int fun()
{
printf("in fun\n");
exit(0);
// return 0;
}
int main(int argc, char const *argv[])
{
printf("hello\n");
// fun();
// printf("world\n");
// return 0;
// exit(0); //结束进程,刷新缓存
// _exit(0); //结束进程,不刷新缓存区
// execl("/bin/ls", "ls", "-l", NULL);
// char *arg[] = {"ls", "-l", NULL};
// execv("/bin/ls", arg);
while(1);
return 0;
}
2.5 守护进程
1、 特点:
守护进程是后台进程,不依赖于控制终端;
生命周期比较长,从运行时开启,系统关闭时结束;
它是脱离控制终端且周期执行的进程。
2. 步骤:
1) 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
2) 在子进程中创建新会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
3) 改变进程运行路径为根目录
原因进程运行的路径不能被卸载;chdir("/")
4) 重设文件权限掩码
目的:增大进程创建文件时权限,提高灵活性;umask(0)
5) 关闭文件描述符
将不需要的文件关闭;close()
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
pid_t id;
if ((id = fork()) < 0)
{
perror("fork err");
return -1;
}
else if (id == 0)
{
//在子进程中创建新会话
setsid();
//修改进程运行路径为根目录
chdir("/");
//修改文件权限掩码
umask(0);
//关闭文件描述符
for (int i = 0; i < 2; i++)
close(i);
int fd;
fd = open("/tmp/info.log", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open err");
return -1;
}
while (1)
{
write(fd, "hello", 5);
sleep(1);
}
}
else
{
exit(0);
}
return 0;
}
五、线程
1.概念
是一个轻量级的进程,为了提高系统的性能引入线程
Linux里同样用task_struct来描述一个线程。
线程和进程都参与统一的调度。
在同一个进程中创建的线程共享该进程的地址空间。
2.线程和进程区别
共性:都为操作系统提供了并发执行能力
不同点:
调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位
地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立
通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源保护的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)
安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全
3.线程函数
3.1 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:thread:线程标识
attr:线程属性, NULL:代表设置默认属性
start_routine:函数名:代表线程函数
arg:用来给前面函数传参
返回值:成功:0
失败:错误码
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
char buf[32] = "";
//线程函数
void *handler(void *arg)
{
sleep(1);
printf("in the thread\n");
printf("num:%d\n", *((int *)arg));
printf("buf:%s\n", buf);
pthread_exit(NULL); //结束线程
}
int main(int argc, char const *argv[])
{
pthread_t tid;
int num = 100;
if(pthread_create(&tid, NULL, handler, &num) != 0)
{
perror("create thread err");
return -1;
}
printf("in the main\n");
printf("main tid:%lu\n", pthread_self());
strcpy(buf, "hello");
//线程回收,阻塞函数,等待子线程结束,回收线程资源
pthread_join(tid, NULL);
return 0;
}
3.2 结束线程
int pthread_exit(void *value_ptr)
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值
返回值:成功 : 0
失败:errno
3.3 回收线程
int pthread_join(pthread_t thread, void **value_ptr)
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象
value_ptr:指针*value_ptr指向线程返回的参数
返回值:成功 : 0
失败:errno
3.4 获取线程号
pthread_t pthread_self(void);
功能:获取线程号
返回值:线程ID
3.5 线程分离
int pthread_detach(pthread_t thread);
功能:让线程分离,线程退出让系统自动回收线程资源
练习:通过线程实现通信。
主线程循环从终端输入数据,子线程循环将数据打印,当输入quit结束程序。
要求:先输入后输出
提示:标志位 int flag = 0;
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char buf[32] = "";
int flag = 0;
void *print(void *arg)
{
while (1)
{
if (flag == 1)
{
if (strcmp(buf, "quit") == 0)
break;
printf("buf:%s\n", buf);
flag = 0;
}
}
}
int main(int argc, char const *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, print, NULL) != 0)
{
perror("create thread err");
return -1;
}
while (1)
{
scanf("%s", buf);
flag = 1;
if (strcmp(buf, "quit") == 0)
break;
}
pthread_join(tid, NULL);
return 0;
}
4、线程同步
4.1 概念
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
4.2 同步机制
通过信号量实现线程间同步。
信号量:由信号量来决定线程是继续运行还是阻塞等待,信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)
信号量的值为非负整数
4.3 特性
P操作:
当信号量的值大于0时,可以申请到资源,申请资源后信号量的值减1
当信号量的值等于0时,申请不到资源,函数阻塞
V操作:
不阻塞,执行到释放操作,信号量的值加1
4.4 函数
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:初始化信号量
参数:sem:初始化的信号量对象
pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用)
value:信号量初值
返回值:成功 0
失败 -1
int sem_wait(sem_t *sem)
功能:申请资源 P操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
int sem_post(sem_t *sem)
功能:释放资源 V操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:释放一次信号量的值加1,函数不阻塞
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char buf[32] = "";
int flag = 0;
void *print(void *arg)
{
while (1)
{
if (flag == 1)
{
if (strcmp(buf, "quit") == 0)
break;
printf("buf:%s\n", buf);
flag = 0;
}
}
}
int main(int argc, char const *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, print, NULL) != 0)
{
perror("create thread err");
return -1;
}
while (1)
{
scanf("%s", buf);
flag = 1;
if (strcmp(buf, "quit") == 0)
break;
}
pthread_join(tid, NULL);
return 0;
}
5.线程互斥
5.1 概念
临界资源:一次仅允许一个进程所使用的资源
临界区:指的是一个访问共享资源的程序片段
互斥:多个线程在访问临界资源时,同一时间只能一个线程访问
互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
5.2 函数接口
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
功能:初始化互斥锁
参数:mutex:互斥锁
attr: 互斥锁属性 // NULL表示缺省属性
返回值:成功 0
失败 -1
int pthread_mutex_lock(pthread_mutex_t *mutex)
功能:申请互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回
int pthread_mutex_unlock(pthread_mutex_t *mutex)
功能:释放互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
int pthread_mutex_destroy(pthread_mutex_t *mutex)
功能:销毁互斥锁
参数:mutex:互斥锁
案例:全局数组int a[10] = {};
t1: 循环倒置数组中元素
t2:循环打印数组中元素
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int a[10] = {0,1,2,3,4,5,6,7,8,9};
pthread_mutex_t lock;
void *print_handler(void *arg)
{
while(1)
{
pthread_mutex_lock(&lock);
for(int i = 0; i < 10; i++)
printf("%d", a[i]);
printf("\n");
pthread_mutex_unlock(&lock);
sleep(1);
}
}
void *swap_handler(void *arg)
{
int t;
while(1)
{
pthread_mutex_lock(&lock);
for(int i = 0; i < 5; i++)
{
t = a[i];
a[i] = a[9-i];
a[9-i] = t;
}
pthread_mutex_unlock(&lock);
}
}
int main(int argc, char const *argv[])
{
pthread_t t1, t2;
pthread_create(&t1, NULL, print_handler, NULL);
pthread_create(&t2, NULL, swap_handler, NULL);
if(pthread_mutex_init(&lock, NULL) != 0)
{
perror("mutex init err");
return -1;
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
5.3 死锁
是指两个或两个以上的进程/线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
6.条件变量
和互斥锁搭配使用实现同步机制
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量
参数:cond:是一个指向结构pthread_cond_t的指针
restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL
返回值:成功:0 失败:非0
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:等待条件的产生
参数:restrict cond:要等待的条件
restrict mutex:对应的锁
返回值:成功:0,失败:不为0
注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁。
int pthread_cond_signal(pthread_cond_t *cond);
功能:产生条件变量
参数:cond:条件变量值
返回值:成功:0,失败:非0
注:必须等待pthread_cond_wait函数先执行,再产生条件才可以
int pthread_cond_destroy(pthread_cond_t *cond);
功能:将条件变量销毁
参数:cond:条件变量值
返回值:成功:0, 失败:非0
pthread_mtex_init(&lock, NULL);
案例:存钱和取钱的例子
主线程循环存钱,子线程循环取钱,每次取100直到余额为0,再进行存钱;
#include <stdio.h>
#include <pthread.h>
int money = 0;
pthread_mutex_t lock;
pthread_cond_t cond, cond1;
//取钱
void *getMoney(void *arg)
{
while(1)
{
pthread_mutex_lock(&lock);
if(money < 100)
pthread_cond_wait(&cond, &lock);
money -= 100;
printf("money:%d\n", money);
if(money < 100)
pthread_cond_signal(&cond1);
pthread_mutex_unlock(&lock);
}
}
int main(int argc, char const *argv[])
{
pthread_t t;
if(pthread_create(&t, NULL, getMoney, NULL) != 0)
{
perror("create thread err");
return -1;
}
//互斥锁
pthread_mutex_init(&lock, NULL);
//条件变量
if(pthread_cond_init(&cond, NULL) != 0)
{
perror("cond init err");
return -1;
}
if(pthread_cond_init(&cond1, NULL) != 0)
{
perror("cond init err");
return -1;
}
//存钱
while(1)
{
pthread_mutex_lock(&lock);
if(money >= 100)
pthread_cond_wait(&cond1, &lock);
scanf("%d", &money); //50
if(money >= 100)
pthread_cond_signal(&cond); //产生条件
pthread_mutex_unlock(&lock);
}
pthread_join(t, NULL);
return 0;
}
六、进程间通信
7种
6种
传统的进程间通信方式:
无名管道、有名管道、信号
system V IPC对象:
共享内存、消息队列、信号灯集
BSD:
套接字(socket)
1.无名管道
1.1 特点
a. 只能用于具有亲缘关系的进程之间的通信
b. 半双工的通信模式,具有固定的读端和写端
c. 管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数.
d. 管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符
fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
1.2 函数接口
int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端 fd[1]:写端
返回值:成功 0
失败 -1
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd[2] = {0};
char buf[65536] = "";
//创建无名管道
if(pipe(fd) < 0)
{
perror("pipe err");
return -1;
}
printf("%d %d\n", fd[0], fd[1]);
//对管道进行读写
// write(fd[1], "hello", 5);
// read(fd[0], buf, 32);
// printf("%s\n", buf);
//1.当管道中没有数据时,读阻塞
// close(fd[1]); //关闭写端
// ssize_t s = read(fd[0], buf, 32);
// printf("%s %d\n", buf, s);
//2.当写满管道时,写阻塞,当至少读出4k空间时,才可以继续写
//管道大小:64k
// write(fd[1], buf, 65536);
// read(fd[0], buf, 4096);
// printf("befor\n");
// write(fd[1], "a", 1);
// printf("after\n");
//3.当读端关闭,写管道时,会导致管道破裂
close(fd[0]);
write(fd[1], "hello", 5); //SIGPIPE
printf("after\n");
return 0;
}
1.3 注意事项
a. 当管道中无数据时,读操作会阻塞;
管道中无数据时,将写端关闭,读操作会立即返回
b. 管道中装满(管道大小64K)数据写阻塞,一旦有4k空间,写继续
c. 只有在管道的读端存在时,向管道中写入数据才有意义。否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号 (通常Broken pipe错误)。
练习:父子进程通信。
父进程循环从终端输入字符串,子进程将字符串循环输出,当输入quit时,程序退出。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
char buf[32] = "";
pid_t id;
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe err");
return -1;
}
if ((id = fork()) < 0)
{
perror("fork err");
return -1;
}
else if (id == 0)
{
while(1)
{
char buf[32] = "";
read(fd[0], buf, 32);
if(strcmp(buf, "quit") == 0)
break;
printf("buf:%s\n", buf);
}
exit(0);
}
else
{
while(1)
{
char buf[32] = "";
// scanf("%s", buf);
fgets(buf, 32, stdin);
write(fd[1], buf, strlen(buf)+1);
if(strcmp(buf, "quit") == 0)
break;
}
wait(NULL);
}
return 0;
}
2.有名管道
2.1 特点
a. 有名管道可以使互不相关的两个进程互相通信。
b. 有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。
c. 进程通过文件IO来操作有名管道
d. 有名管道遵循先进先出规则
e. 不支持如lseek() 操作
2.2 函数接口
int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
mode:权限
返回值:成功:0
失败:-1,并设置errno号
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd;
//创建管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
printf("file exists\n");
else
{
perror("mkfifo err");
return -1;
}
}
printf("mkfifo ok\n");
//打开管道
fd = open("./fifo", O_RDWR);
if(fd < 0)
{
perror("open err");
return -1;
}
char buf[32] = "hello";
char data[32] = "";
write(fd, buf, strlen(buf));
read(fd, data, 32);
printf("%s\n", data);
return 0;
}
2.3 注意事项
a. 只写方式,写阻塞,一直到另一个进程把读打开
b. 只读方式,读阻塞,一直到另一个进程把写打开
c. 可读可写,如果管道中没有数据,读阻塞
练习:实现两个不相关进程间通信。
read.c : 从终端读取数据
write.c : 向终端输出数据
当输入quit时结束。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd;
char buf[32] = "";
//创建管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
printf("file exists\n");
else
{
perror("mkfifo err");
return -1;
}
}
printf("mkfifo ok\n");
//打开管道
fd = open("./fifo", O_WRONLY);
if(fd < 0)
{
perror("open err");
return -1;
}
while(1)
{
scanf("%s", buf);
write(fd, buf, strlen(buf)+1);
if(!strcmp(buf, "quit"))
break;
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd;
char buf[32] = "";
//创建管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
printf("file exists\n");
else
{
perror("mkfifo err");
return -1;
}
}
printf("mkfifo ok\n");
//打开管道
fd = open("./fifo", O_RDONLY);
if(fd < 0)
{
perror("open err");
return -1;
}
while(1)
{
read(fd, buf, 32);
if(!strcmp(buf, "quit"))
break;
printf("buf:%s\n", buf);
}
return 0;
}
2.4 有名管道和无名管道区别
无名管道 | 有名管道 | |
特点 | 只能在亲缘关系进程间使用 半双工通信方式 有固定的读端和写端,fd[0]:读,fd[1]:写端 通过文件IO进行操作 步骤:创建管道、读写操作 | 不相关的任意进程间使用 在路径中有管道文件,实际数据存在内核空间 通过文件IO进行操作 步骤:创建管道、打开管道、读写操作 |
函数 | pipe | mkfifo |
读写特性 | 当管道中没有数据,读阻塞 当写满管道时,写阻塞 |
3.信号
3.1 概念
1)信号是在软件层次上对中断机制的一种模拟,是一种 异步通信方式
2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
3.2. 信号的响应方式
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作
3.3. 信号种类
SIGKILL:结束进程,不能被忽略不能被捕捉
SIGSTOP:结束进程,不能被忽略不能被捕捉
SIGCHLD:子进程状态改变时给父进程发的信号,不会结束进程
SIGINT:结束进程,对应快捷方式ctrl+c
SIGTSTP:暂停信号,对应快捷方式ctrl+z
SIGQUIT:退出信号,对应快捷方式ctrl+\
SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。
SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
3.4 函数接口
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
sig:要发送的信号
返回值:成功 0
失败 -1
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0
失败 -1
int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号
SIG_DFL:执行默认操作
handler:捕捉信号 void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
失败:-1
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
// kill(getpid(), SIGKILL);
raise(SIGKILL); //给自己发信号
// while (1)
// ;
while (1)
pause(); //将当前进程挂起(阻塞),直到收到信号结束
return 0;
}
typedef void (*sighandler_t)(int); //typedef unsigned int INT;
typedef void (*)(int) sighandler_t;
sighandler_t signal(int signum, void (*handler)(int) );
void handler(int sig)
{
if(sig == SIGINT)
printf("xxx\n");
else if(sig == SIGQUIT)
}
signal(SIGINT, handler);
signal(SIGQUIT, handler)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig)
{
printf("ctrl+c\n");
}
int main(int argc, char const *argv[])
{
// signal(SIGINT, SIG_IGN);//忽略信号
// signal(SIGINT, SIG_DFL); //执行默认操作
signal(SIGINT, handler); //捕捉信号
while(1)
pause();
return 0;
}
作业:
- 两个进程实现cp功能
./r srcfile
./w newfile
- 用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
司机:父进程
捕捉信号:
忽略信号:
售票员:子进程
捕捉信号:
忽略信号:
./a.out
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
pid_t pid;
void driver(int sig)
{
if (sig == SIGUSR1)
printf("let's gogogo\n");
else if (sig == SIGUSR2)
printf("stop the bus\n");
else if (sig == SIGTSTP)
{
kill(pid, SIGUSR1);
wait(NULL);
exit(0);
}
}
void saler(int sig)
{
if (sig == SIGINT)
kill(getppid(), SIGUSR1);
else if (sig == SIGQUIT)
kill(getppid(), SIGUSR2);
else if (sig == SIGUSR1)
{
printf("please get off the bus\n");
exit(0);
}
}
int main(int argc, char const *argv[])
{
if ((pid = fork()) < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
signal(SIGINT, saler);
signal(SIGQUIT, saler);
signal(SIGUSR1, saler);
signal(SIGTSTP, SIG_IGN);
}
else
{
signal(SIGUSR1, driver);
signal(SIGUSR2, driver);
signal(SIGTSTP, driver);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
}
while (1)
pause();
return 0;
}
4.共享内存
4.1 特点
1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
3)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
4)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
4.2 步骤
0)创建key值
1)创建或打开共享内存
2)映射
3)取消映射
4)删除共享内存
4.3 函数接口
key_t ftok(const char *pathname, int proj_id);
功能:创建key值
参数:pathname:文件名
proj_id:取整型数的低8位数值
返回值:成功:key值
失败:-1
int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
key 键值
size 共享内存的大小
shmflg IPC_CREAT|IPC_EXCL|0777
返回值:成功 shmid
出错 -1
void *shmat(int shmid,const void *shmaddr,int shmflg);
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:
shmid 共享内存的id号
shmaddr 一般为NULL,表示由系统自动完成映射
如果不为NULL,那么有用户指定
shmflg:SHM_RDONLY就是对该共享内存只进行读操作
0 可读可写
返回值:成功:完成映射后的地址,
出错:-1的地址
用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)
int shmdt(const void *shmaddr);
功能:取消映射
参数:要取消的地址
返回值:成功0
失败的-1
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
功能:(删除共享内存),对共享内存进行各种操作
参数:
shmid 共享内存的id号
cmd IPC_STAT 获得shmid属性信息,存放在第三参数
IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
IPC_RMID:删除共享内存,此时第三个参数为NULL即可
返回:成功0
失败-1
用法:shmctl(shmid,IPC_RMID,NULL);
查看共享内存的命令:
ipcs -m
删除共享内存的命令:
ipcrm -m shmid
代码案例:
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建或打印共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
printf("shmid:%d\n", shmid);
//映射
char *p = NULL;
p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
strcpy(p, "hello");
printf("%s\n", p);
//取消映射
shmdt(p);
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
练习:一个进程从终端输入,另一个进程将数据输出,借助共享内存通信。
要求:当输入quit时程序退出
同步:标志位
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
struct msg
{
int flg;
char buf[32];
};
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建或打印共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
printf("shmid:%d\n", shmid);
//映射
struct msg *p = NULL;
p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
if(p == (struct msg *)-1)
{
perror("shmat err");
return -1;
}
p->flg = 0;
while(1)
{
scanf("%s", p->buf);
p->flg = 1;
if(strcmp(p->buf, "quit") == 0)
break;
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建或打印共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
printf("shmid:%d\n", shmid);
//映射
char *p = NULL;
p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
strcpy(p, "hello");
printf("%s\n", p);
//取消映射
shmdt(p);
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
5.信号灯集
5.1. 特点:
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作
5.2 步骤:
0)创建key值
1)创建或打开信号灯集 semget
2)初始化信号灯集 semctl
3)pv操作 semop
4)删除信号灯集 semctl
5.3 函数接口
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:成功:信号灯集ID
失败:-1
int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
opsptr:操作方式
nops: 要操作的信号灯的个数 1个
返回值:成功 :0
失败:-1
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
用法:
申请资源 P操作:
mysembuf.sem_num = 0;
mysembuf.sem_op = -1;
mysembuf.sem_flg = 0;
semop(semid, &mysembuf, 1);
释放资源 V操作:
mysembuf.sem_num = 0;
mysembuf.sem_op = 1;
mysembuf.sem_flg = 0;
semop(semid, &mysembuf, 1);
int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
semnum: 要操作的集合中的信号灯编号
cmd:
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值,需要用到第四个参数:共用体
IPC_RMID:从系统中删除信号灯集合
返回值:成功 0
失败 -1
用法:初始化:
union semun{
int val; //信号灯的初值
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值
删除信号灯集:semctl(semid, 0, IPC_RMID);
5.4 命令
ipcs -s:查看信号灯集
ipcrm -s semid:删除信号灯集
例子:
union semun {
int val; //信号灯的初值
};
int main(int argc, char const *argv[])
{
key_t key;
int semid;
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建或打开信号灯集
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid < 0)
{
if (errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{
//初始化
union semun sem;
sem.val = 10;
semctl(semid, 0, SETVAL, sem); //对编号为0的信号灯初值设置为10
sem.val = 0;
semctl(semid, 1, SETVAL, sem); //对编号为1的信号灯初值设置为0
}
printf("semid:%d\n", semid);
printf("%d\n",semctl(semid, 0, GETVAL));//获取编号为0的信号灯的值
printf("%d\n",semctl(semid, 1, GETVAL));//获取编号为1的信号灯的值
//pv操作
//p操作:申请资源
struct sembuf buf;
buf.sem_num = 0; //信号灯的编号
buf.sem_op = -1; //p操作
buf.sem_flg = 0; //阻塞
semop(semid, &buf, 1);
//v操作:释放资源
buf.sem_num = 1;
buf.sem_op = 1; //v操作
buf.sem_flg = 0;
semop(semid, &buf, 1);
printf("%d\n",semctl(semid, 0, GETVAL));//获取编号为0的信号灯的值
printf("%d\n",semctl(semid, 1, GETVAL));//获取编号为1的信号灯的值
//删除信号灯集
semctl(semid, 0, IPC_RMID); //指定任意一个编号即可删除信号灯集
return 0;
//input.c代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>
union semun {
int val; //信号灯的初值
};
//初始化
void seminit(int semid, int snum, int val)
{
union semun sem;
sem.val = val;
semctl(semid, snum, SETVAL, sem);
}
//pv操作
void sem_op(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
key_t key;
int shmid, semid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建或打印共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
printf("shmid:%d\n", shmid);
//创建或打开信号灯集
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if (semid < 0)
{
if (errno == EEXIST)
semid = semget(key, 1, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{
//初始化
seminit(semid, 0, 0);
}
//映射
char *p = NULL;
p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
while(1)
{
scanf("%s", p);
//释放资源
sem_op(semid, 0, 1);
if(strcmp(p, "quit") == 0)
break;
}
return 0;
}
//output.c代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>
union semun {
int val; //信号灯的初值
};
//初始化
void seminit(int semid, int snum, int val)
{
union semun sem;
sem.val = val;
semctl(semid, snum, SETVAL, sem);
}
//pv操作
void sem_op(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
key_t key;
int shmid, semid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建或打印共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
printf("shmid:%d\n", shmid);
//创建或打开信号灯集
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if (semid < 0)
{
if (errno == EEXIST)
semid = semget(key, 1, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{
//初始化
seminit(semid, 0, 0);
}
//映射
char *p = NULL;
p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
while(1)
{
//申请资源
sem_op(semid, 0, -1);
if(strcmp(p, "quit") == 0)
break;
printf("data:%s\n", p);
}
//取消映射
shmdt(p);
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
//删除信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
6.消息队列
6.1 特点:
消息队列是IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
消息队列可以按照类型来发送/接收消息
6.2 步骤:
1)创建key值 ftok
2)创建或打开消息队列 msgget
3)添加消息/读取消息 msgsnd/msgrcv
4)删除消息队列 msgctl
6.3 操作命令
ipcs -q :查看消息队列
ipcrm -q msgid :删除消息队列
6.4. 函数接口
int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数: key值
flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
失败:-1
int msgsnd(int msqid, const void *msgp, size_t size, int flag);
功能:添加消息
参数:msqid:消息队列的ID
msgp:指向消息的指针。常用消息结构msgbuf如下:
struct msgbuf{
long mtype; //消息类型
char mtext[N]}; //消息正文
size:发送的消息正文的字节数
flag:IPC_NOWAIT消息没有发送完成函数也会立即返回
0:直到发送完成函数才返回
返回值:成功:0
失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
功能:读取消息
参数:msgid:消息队列的ID
msgp:存放读取消息的空间
size:接受的消息正文的字节数
msgtype:0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息.
小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
flag:0:若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
失败:-1
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
cmd:
IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
IPC_SET:设置消息队列的属性。这个值取自buf参数。
IPC_RMID:从系统中删除消息队列。
buf:消息队列缓冲区
返回值:成功:0
失败:-1
用法:msgctl(msgid, IPC_RMID, NULL)
例子:
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>
struct msgbuf{
long type;
int num;
char buf[32];
};
int main(int argc, char const *argv[])
{
key_t key;
int msgid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建消息队列
msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
if(msgid < 0)
{
if(errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return -1;
}
}
//添加消息
int size = sizeof(struct msgbuf)-sizeof(long);
struct msgbuf msg = {1, 100, "hello"};
struct msgbuf msg1 = {2, 200, "world"};
msgsnd(msgid, &msg, size, 0);
msgsnd(msgid, &msg1, size, 0);
//读取消息
struct msgbuf m;
msgrcv(msgid, &m, size, 2, 0);
printf("%d %s\n", m.num, m.buf);
//删除消息队列
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
练习:两个进程通过消息队列进行通信,一个进程从终端输入下发的指令,另一个进程接收指令,并打印对应操作语句。
如果输入LED ON,另一个进程输出 “开灯”
如果输入LED OFF,另一个进程输出 “关灯”
#include <stdio.h> //send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>
struct msgbuf
{
long type;
char buf[32];
};
int main(int argc, char const *argv[])
{
key_t key;
int msgid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建消息队列
msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
if(msgid < 0)
{
if(errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return -1;
}
}
struct msgbuf msg;
msg.type = 1;
int s = sizeof(struct msgbuf)-sizeof(long);
while(1)
{
scanf("%s", msg.buf);
msgsnd(msgid, &msg, s, 0);
}
return 0;
}
#include <stdio.h> //recieve.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>
struct msgbuf
{
long type;
char buf[32];
};
int main(int argc, char const *argv[])
{
key_t key;
int msgid;
//创建key值
key = ftok("./app", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建消息队列
msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
if(msgid < 0)
{
if(errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return -1;
}
}
struct msgbuf msg;
int s = sizeof(struct msgbuf)-sizeof(long);
while(1)
{
msgrcv(msgid, &msg, s, 1, 0);
if(strcmp(msg.buf, "LEDON") == 0)
printf("开灯\n");
else if(strcmp(msg.buf, "LEDOFF") == 0)
printf("关灯\n");
}
return 0;
}