1. 其他知识点
1.1. man 手册
1.2. ctags追踪
ctags索引:
- vi -t 要查找的内容,(查找宏,数据类型等)
输入前面序号,回车 - 继续追踪
将光标定位到要追踪的内容上,ctrl+]
回退:ctrl+t
1.3. exec函数族 在一个进程里 运行其他进程
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
代码:在一个进程里 运行其他进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define SYSTEM_FLAG 1
#define EXEC_FLAG_1 1
#define EXEC_FLAG_2 1
/* 常用的:在一个进程里面,运行其他进程 */
int main(int argc, char *argv[])
{
#if SYSTEM_FLAG //system 族
system("ls -l");
#endif
#if EXEC_FLAG_1 //exec 族
/* 进程所在目录、进程文件名字、参数、用NULL结尾 */
execl("/bin/ls", "ls", "-l", NULL);
#endif
#if EXEC_FLAG_2 //exec 族
/* 参数为可变参 */
execl("./", "./a.out", NULL);
#endif
}
1.4. 区别:线程互斥信号量、进程早期信号通知、进程IPC V信号灯集
重点关注,不要混淆
1.4.1. 进程早期信号通知
指的是:系统进程和应用进程等,进程之间通知的信号。
1.4.2. 线程互斥信号量
主要用于PV操作的,单个信号量个体
1.4.3. 进程IPC V信号灯集
在 线程互斥信号量
的基础上升级,能够以集合的形式,操作多个单体信号量
1.5. 信号灯集、消息队列,返回id为0怎么处理?(共享内存无该问题)
很多种原因,暂时不会解决。
不能总是删了重新运行,这不是个办法
2. 标准IO
2.1. 练习:通过fgetc与fputc函数实现cat功能.
/* 练习:通过fgetc与fputc函数实现cat功能.
注意:fgetc官方 返回值为int,不用char
字符补码保存;若变量ch 为char类型读到字符0xFF与EOF(-1)比较时相等,误判为已经读到文件末尾
*/
#include <stdio.h>
int main(int argc, char *argv[])
{
// argv[0]是文件名字,1是后续第一个参数
FILE *fp = fopen(argv[1], "r");
if (NULL == fp)
{
perror("fopen err");
return -1;
}
// 不到EOF 且同时 不读出错,则 循环 读取 打印 流
int ch;
while (((ch = fgetc(fp)) != EOF) && !ferror(fp))
fputc(ch, stdout);
putchar(10);
// 关闭文件流
fclose(fp);
}
2.2. 用标准IO实现cp功能
diff 文件名1 文件名2 :对比文件1 与文件2是否有不相同的地方
#include <stdio.h>
int main(int argc, char *argv[])
{
char buf[32];
// 打开 源文件(被复制的文件)
FILE *src = fopen(argv[1], "r");
if (NULL == src)
{
perror("src err");
return -1;
}
// 打开 目标文件(复制后的文件)
FILE *dest = fopen(argv[2], "w");
if (NULL == dest)
{
perror("dest err");
return -1;
}
// 循环 读源文件 写入 目标文件
while (fgets(buf, sizeof(buf), src) != NULL)
fputs(buf, dest);
// 关闭 流
fclose(src);
fclose(dest);
}
2.3. 实现“head -n 文件名”命令的功能 (显示文件前n行)
例:head -3 test.c -> ./a.out -3 test.c
atoi : "1234" -- 1234
atoi:将字符串转化为整型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
if (argc != 3)
{
perror("err input");
return -1;
}
FILE *fp = fopen(argv[2], "r");
if (fp == NULL)
{
perror("open err");
return -1;
}
// 读取n行,参数为字符串转换为整形
// int num = -arot(argv[1]);
// int num = -1 * arot(argv[1]);
int num = atoi(argv[1] + 1); //+1为从-的身后数字开始
// 循环读取 并输出
// fgets会在读到如下情况停止:\n,文件结束
// 读到末尾会返回NULL,所以可 不用判断 num等于0,就已经结束了
while (fgets(buf, 32, fp) != NULL)
{
// 字符长度 比 下标大1
if (buf[(strlen(buf) - 1)] == '\n')
num--; //几个\n就是输出了几行
fputs(buf, stdout);
memset(buf, 0, sizeof(buf) / sizeof(*buf));
// 还有几行要打印
if (num == 0)
break;
}
fclose(fp);
return 0;
}
2.4. 编程读写一个文件test.txt,每隔1秒向文件中写入一行数据
类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl+C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
3, 2007-7-30 15:16:42
4, 2007-7-30 15:16:43
5, 2007-7-30 15:19:02
6, 2007-7-30 15:19:03
7, 2007-7-30 15:19:04
分析:
1. 打开文件fopen,以a+的形式打开,循环往文件写内容
2. 每隔1s写入一行,sleep(1);
3. 计算文件行数,wc -l
4. 计算当前时间,转换成年月日、时分秒,time,localtime
5.字符串拼接函数:strcpy/strcat(dest, src)、sprintf、fprintf
6.全缓冲
//time:计算时间,获取秒数
//localtime:将时间秒数转化成当地年月日时分秒
//fprintf:()
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
// 追加打开 或 创建文件
FILE *fp = fopen("./test.txt", "a+");
if (NULL == fp)
{
perror("err fopen");
return -1;
}
// 计算文件行数
char buf[32] = {0};
int line = 0;
while (fgets(buf, sizeof(buf), fp) != NULL)
if (buf[strlen(buf) - 1] == '\n')
line++;
// 循环向文件中写
while (1)
{
// 获取 当前时间
time_t t = time(NULL);
// 转化 时间秒数 为年月日十分秒
struct tm *tm = localtime(&t);
// 向文件写入,属于文件缓冲,需要强制刷新,写入进去
fprintf(fp, "%d,%d-%d-%d %d:%d:%d\n",
++line,
tm->tm_year + 1900, //在1900基础开始,需要+1900
tm->tm_mon + 1, //月份从0开始,需要+1
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
// 强制刷新
fflush(fp);
sleep(1);
}
// 关闭 文件流
fclose(fp);
}
3. 文件IO
3.1. 用文件IO实现cp功能。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
ssize_t s = 0;
// 打开目标文件和源文件
int src = open(argv[1], O_RDONLY);
int dest = open(argv[2], O_TRUNC | O_WRONLY | O_CREAT, 0666);
if (dest < 0 || src < 0)
{
perror("open err");
return -1;
}
// 循环读写
while ((s = read(src, buf, sizeof(buf))) != 0)
{
// 读多少,写多少
write(dest, buf, s);
}
close(src);
close(dest);
}
4. 进程
4.1. cp拷贝 父子进程 文件IO
要求:文件IO cp src dest
通过父子进程完成对文件的拷贝(cp),父进程从文件开始到文件的一半开始拷贝,子进程从文件的一半到文件末尾。
提示:
- 长度获取:lseek() 400-->200
- 定位到一半lseek
- buf[32] 200-32-32 read
- fork之前打开文件还是fork之后打开文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#define N 32
int main(int argc, char *argv[])
{
// 打开 源文件 目标文件
int src = open(argv[1], O_RDONLY);
int dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (src < 0 || dest < 0)
{
perror("open src dest err");
return -1;
}
ssize_t s = 0; //存储每次读到的个数
char buf[N] = {0}; // 容器数组
/* 先让父进程执行。不想用老师的延时阻塞方法 */
// 计算一半位置:控制父进程,先只读前半段,而不多读
// lseek 定位到EOF位置,即最大下标+1,即有效字符个数
off_t len_half = lseek(src, 0, SEEK_END) / 2;
lseek(src, 0, SEEK_SET);
while (1)
{
// 不管奇偶数长度,父进程,总会把正中间前面的都读走
// 父进程,要读写的长度 为正数,即还存在 要读写的数据
// len为形参传入if,不会修改值
if (len_half - N > 0)
{
len_half -= N; //减少 要读写的长度
s = read(src, buf, N); //读到末尾会返回0
write(dest, buf, s); //写入 读到的长度个数
}
else
{
//剩下要读的长度,不够数组的长度,即最后一回要读的
s = read(src, buf, len_half);
write(dest, buf, s);
break; //不终止,则会父进程全部cp完,并死循环
}
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
while (1)
{
// 文件指针,经过父进程操作,到了正中间位置
// 子进程,从正中间位置开始,一直到最后
if ((s = read(src, buf, N)) > 0) //读到末尾会返回0
write(dest, buf, s); //写入 读到的长度个数
else
exit(0); //进程结束,会自动关闭文件描述符
}
}
else
{
// 子进程exit退出了,一部分资源被系统自动回收
// 但还有 终止状态等资源,需要父进程回收,或父进程退出后,系统自动回收
waitpid(pid, NULL, 0);
}
s
close(src); //养成好习惯
close(dest); //手动 各自关闭各自的
//返回给系统调用:在main函数里,return(0)和exit(0)是一样的
//但对于子进程来说,子进程的main函数不会直接返回给任何特定的实体(可不写return是)
return 0;
}
5. 线程
5.1. (标志位)通过线程 实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。
分析:
- 全局变量
- 全局变量--->标志位
-
- 可输出状态(输入完成)
- 不可输出状态(打印完成)
char buf[32]
fgets()
printf(buf)
// 通过线程 实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char buf[32] = {0};
int flag = 0; //等待写入,不可读取
// 子线程循环输出
void *myprint(void *arg)
{
while (1)
{
if (flag == 1)
{
if (strcmp(buf, "quit") == 0)
pthread_exit(NULL);
else
{
printf("buf:%s\n", buf);
flag = 0; //等待写入,不可读取
}
}
}
}
int main(int argc, char *argv[])
{
pthread_t pid;
if (pthread_create(&pid, NULL, myprint, NULL) != 0)
{
perror("create err pthread");
return -1;
}
// 主线程循环输入
while (1)
{
fgets(buf, 32, stdin); //会把\n也获取进来,因为在\n截断
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
flag = 1; //写入完成,读取就绪
if (strcmp(buf, "quit") == 0)
break;
}
pthread_join(pid, NULL); //阻塞等待回收
return 0;
}
5.2. (信号量)通过线程 实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>
char buf[32] = {0};
sem_t sem; //线程 信号量对象
// 子线程循环输出
void *myprint(void *arg)
{
while (1)
{
//等待 可以被读取的 资源 信号量
sem_wait(&sem); //-1
if (strcmp(buf, "quit") == 0)
pthread_exit(NULL);
else
printf("buf:%s\n", buf);
}
}
int main(int argc, char *argv[])
{
// 初始化 给线程用的 信号量
if (sem_init(&sem, 0, 0) != 0) //成功返回0
{
perror("sem_init err");
return -1;
}
// 创建线程
pthread_t pid;
if (pthread_create(&pid, NULL, myprint, NULL) != 0)
{
perror("create err pthread");
return -1;
}
// 主线程循环输入
while (1)
{
fgets(buf, 32, stdin); //会把\n也获取进来,因为在\n截断
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
//写入完成,可被读取,已增加资源资源
sem_post(&sem); // +1
if (strcmp(buf, "quit") == 0)
break;
}
pthread_join(pid, NULL); //阻塞等待回收
//删除信号量
sem_destroy(&sem);
return 0;
}
5.3. (互斥锁)(仅完整互斥,不保证同步顺序)通过两个线程实现数组倒置,线程一用于循环倒置,线程二用于循环打印。
// 练习:通过两个线程实现数组倒置,线程一用于循环倒置,线程二用于循环打印。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock; //互斥锁 互斥量
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
void *rever(void *arg)
{
pthread_mutex_lock(&lock); //阻塞:申请上锁
// 倒置
for (int i = 0; i < 5; i++)
{
int temp = arr[i];
arr[i] = arr[9 - i];
arr[9 - i] = temp;
}
pthread_mutex_unlock(&lock); //解锁
}
void *myprint(void *arg)
{
pthread_mutex_lock(&lock); //阻塞:申请上锁
// 打印
for (int i = 0; i < 10; i++)
printf("%d ", arr[i]);
putchar(10);
pthread_mutex_unlock(&lock); //解锁
sleep(1);
}
int main(int argc, char *argv[])
{
// 若在循环下:虽然保证了操作完整性,但谁先后执行的同步顺序,无法保证
// 初始化锁
if (pthread_mutex_init(&lock, NULL))
{
perror("mutex err");
return -1;
}
// 创建线程
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, rever, NULL))
{
perror("tid1 err");
return -1;
}
if (pthread_create(&tid2, NULL, myprint, NULL))
{
perror("tid2 err");
return -1;
}
// 回收线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 销毁锁
pthread_mutex_destroy(&lock);
return 0;
}
5.4. (同步:条件变量+互斥锁)通过两个线程实现数组倒置,线程一用于循环倒置,线程二用于循环打印。用互斥锁+条件变量实现倒置一次,打印一次。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock; //互斥锁(互斥量)
pthread_cond_t cond; //条件变量,配合互斥锁使用
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
/* 打印 和 翻转 必须循环顺序来,不可乱序 */
/* 逻辑:
双子进程运行,rever因sleep阻塞,myprint先上锁,后被 条件变量阻塞等待条件又解锁
sleep结束,rever拿到锁上锁,完成倒置即产生条件通知给条件变量,随后解锁,又进入sleep后 阻塞等待申请锁
此时myprint拿到锁,条件变量因为有条件了,就上锁,但因用互斥锁,所以就这么用,不会产生重复申请锁导致死锁
打印后释放锁,又上了锁,后被条件变量解锁,来阻塞等待。此时rever重复sleep后上锁的循环
*/
void *rever(void *arg)
{
while (1)
{
sleep(1);
pthread_mutex_lock(&lock); //阻塞:申请上锁
// 倒置
for (int i = 0; i < 5; i++)
{
int temp = arr[i];
arr[i] = arr[9 - i];
arr[9 - i] = temp;
}
pthread_cond_signal(&cond); //产生条件
pthread_mutex_unlock(&lock); //解锁
}
}
void *myprint(void *arg)
{
while (1)
{
pthread_mutex_lock(&lock); //阻塞:申请上锁
pthread_cond_wait(&cond, &lock); //阻塞等待条件:解锁 -|- 等条件来了:上锁
// 打印
for (int i = 0; i < 10; i++)
printf("%d ", arr[i]);
putchar(10);
pthread_mutex_unlock(&lock); //解锁
}
}
int main(int argc, char *argv[])
{
// 用 条件变量 和 互斥锁,保证了同步顺序
// 初始化锁
if (pthread_mutex_init(&lock, NULL))
{
perror("mutex err");
return -1;
}
// 初始化条件变量
if (pthread_cond_init(&cond, NULL))
{
perror("cond err");
return -1;
}
// 创建线程
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, rever, NULL))
{
perror("tid1 err");
return -1;
}
if (pthread_create(&tid2, NULL, myprint, NULL))
{
perror("tid2 err");
return -1;
}
// 回收线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 销毁锁
pthread_mutex_destroy(&lock);
// 销毁条件变量
pthread_cond_destroy(&cond);
return 0;
}
6. 无名管道
6.1. 无名管道:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int fd[2] = {0}; //无名管道
char buf[32] = {0}; //缓冲区
// 创建无名管道
if (pipe(fd) < 0)
{
perror("pipe err");
return -1;
}
// 创建父子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
// 子进程,从pipe读取数据,进而循环打印
while (1)
{
// 从pipe读
read(fd[0], buf, sizeof(buf));
if (strcmp(buf, "quit") == 0)
exit(0);
else
printf("buf:%s\n", buf);
}
}
else
{
// 循环输入
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
write(fd[1], buf, sizeof(buf));
if (strcmp(buf, "quit") == 0)
exit(0);
}
wait(NULL);
}
return 0;
}
7. 有名管道
7.1. 有名管道:通过两个进程实现cp功能
- 通过两个代码,第一个in,第二个out
- 由有名管道特性,单个进程只打开 读或者写 会阻塞,直到另一端打开 对应的 写或者读。
- 并且 有名管道,自带流量控制,不用担心读写过程
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
ssize_t s = 0;
// 创建 有名管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
puts("fifo file exist");
else
{
perror("mkfifo err");
return -1;
}
}
// 打开文件
// 只需要读取
int src = open(argv[1], O_RDONLY);
if (src < 0)
{
perror("file open err");
return -1;
}
// 打开有名管道
// 只需要写入
int fd_fifo = open("./fifo", O_WRONLY);
if (fd_fifo < 0)
{
perror("open fd err");
return -1;
}
// 循环从源文件,读取数据到fifo
while ((s = read(src, buf, sizeof(buf))) > 0)
write(fd_fifo, buf, s);
// 关闭文件描述符号
close(src);
close(fd_fifo);
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
ssize_t s = 0;
// 创建 有名管道
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
puts("fifo file exist");
else
{
perror("mkfifo err");
return -1;
}
}
// 打开文件
// 只需要写入
int dest = open(argv[1], O_TRUNC | O_CREAT | O_WRONLY, 0666);
if (dest < 0)
{
perror("file open err");
return -1;
}
// 打开有名管道
// 只需要读取
int fd_fifo = open("./fifo", O_RDONLY);
if (fd_fifo < 0)
{
perror("open fd err");
return -1;
}
// 循环从源文件,读取数据到fifo
while ((s = read(fd_fifo, buf, sizeof(buf))) > 0)
write(dest, buf, s);
// 关闭文件描述符号
close(dest);
close(fd_fifo);
return 0;
}
7.2. 借助有名管道结合多进程实现两个人聊天(全双工通信),当输入quit的时候退出通信。
user1:
// 练习:借助有名管道结合 多进程 实现两个人聊天(全双工通信),当输入quit的时候退出通信。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//1.创建管道 两个
if (mkfifo("./fifo1", 0777) < 0)
{
if (errno == EEXIST)
{
printf("fifo1 eexist.\n");
}
else
{
perror("mkfifo fifo1 err.");
return -1;
}
}
if (mkfifo("./fifo2", 0777) < 0)
{
if (errno == EEXIST)
{
printf("fifo2 eexist.\n");
}
else
{
perror("mkfifo fifo2 err.");
return -1;
}
}
//2.打开管道
/* 注意 有名管道 打开时候的阻塞
管道 只写,阻塞的是open,直到读打开,
管道 只读,阻塞的也是open,直到写打开
必须先写,才能读。如果没数据就读,就会崩溃
看笔记的参考
*/
int fd_r = open("./fifo1", O_RDONLY);
if (fd_r < 0)
{
perror("fifo1 open err.");
return -1;
}
puts("1 read open ");
int fd_w = open("./fifo2", O_WRONLY);
if (fd_w < 0)
{
perror("fifo2 open err.");
return -1;
}
puts("1 write open ");
puts("open fifo 1 2 ok in 111");
//3.一个进程循环接收,一个进程循环在发送
char buf[128];
pid_t pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0)
{
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
write(fd_w, buf, sizeof(buf));
if (strncmp(buf, "quit", 4) == 0)
break;
}
kill(getppid(), SIGKILL);
exit(0);
}
else
{
while (1)
{
read(fd_r, buf, sizeof(buf));
printf("buf=%s\n", buf);
if (strncmp(buf, "quit", 4) == 0)
break;
}
kill(pid, SIGKILL);
wait(NULL);
}
close(fd_r);
close(fd_w);
return 0;
}
user2:
// 练习:借助有名管道结合 多进程 实现两个人聊天(全双工通信),当输入quit的时候退出通信。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//1.创建管道 两个
if (mkfifo("./fifo1", 0777) < 0)
{
if (errno == EEXIST)
{
printf("fifo1 eexist.\n");
}
else
{
perror("mkfifo fifo1 err.");
return -1;
}
}
if (mkfifo("./fifo2", 0777) < 0)
{
if (errno == EEXIST)
{
printf("fifo2 eexist.\n");
}
else
{
perror("mkfifo fifo2 err.");
return -1;
}
}
//2.打开管道
// 注意 打开顺序,会阻塞
int fd_w = open("./fifo1", O_WRONLY);
if (fd_w < 0)
{
perror("fifo1 open err.");
return -1;
}
puts("2 write open ");
int fd_r = open("./fifo2", O_RDONLY);
if (fd_r < 0)
{
perror("fifo2 open err.");
return -1;
}
puts("2 write open ");
puts("open fifo 1 2 ok in 222");
//3.一个进程循环接收,一个进程循环在发送
char buf[128];
pid_t pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0)
{
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
write(fd_w, buf, sizeof(buf));
if (strncmp(buf, "quit", 4) == 0)
break;
}
kill(getppid(), SIGKILL);
exit(0);
}
else
{
while (1)
{
read(fd_r, buf, sizeof(buf));
printf("buf=%s\n", buf);
if (strncmp(buf, "quit", 4) == 0)
break;
}
kill(pid, SIGKILL);
wait(NULL);
}
close(fd_r);
close(fd_w);
return 0;
}
8. 信号捕捉
8.1. 用信号的知识实现司机和售票员问题。
- 售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
- 售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
- 司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
- 司机等待售票员下车,之后司机再下车。
思路:父子进程:
- 父进程:司机:
-
- 忽略:SIGINT、SIGQUIT
- 捕捉:
-
-
- SIGUSR1--》printf :gogogo
- SIGUSR2--》printf :stop the bus
- SIGTSTP--》kill--》SIGUSR1--》wait--》exit
-
- 子进程:售票员:
-
- 忽略:SIGTSTP
- 捕捉:
-
-
- SIGINT--》kill--》SIGUSR1
- SIGQUIT--》kill--》SIGUSR2
- SIGUSR1--》printf:get off the bus--》exit
-
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t pid; //会被更新
void driver(int sig)
{
if (SIGUSR1 == sig)
puts("driver:gogogog");
else if (SIGUSR2 == sig)
puts("stop the bus");
else if (SIGTSTP == sig)
{
kill(pid, SIGUSR1);
wait(NULL); //售票员先下车
exit(0); //子进程退出后,自己也推出
}
}
void saler(int sig)
{
if (SIGINT == sig)
kill(getppid(), SIGUSR1);
else if (SIGQUIT == sig)
kill(getppid(), SIGUSR2);
else if (SIGUSR1 == sig)
{
puts("saler:get off the bus");
exit(0); //子进程 被要求 下车退出
}
}
int main(int argc, char *argv[])
{
// 创建 父子进程
pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程:售票员
{
signal(SIGTSTP, SIG_IGN);
signal(SIGINT, saler); //ctrl + c
signal(SIGQUIT, saler); //ctrl + '\'
signal(SIGUSR1, saler); //ctrl + z
}
else //父进程:司机
{
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGUSR1, driver);
signal(SIGUSR2, driver);
signal(SIGTSTP, driver);
}
while (1)
pause(); //父子进程,都在循环 等待接收信号
return 0;
}
9. 共享内存
9.1. 两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束
共享内存没有同步,实现输入一次打印一次。
标志位:判断是否是可输出状态
要让两个进程拿到的:char buf[32]
int flag
结构体
struct shm{
char buf[32];
int flag;
}
in:
// 两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
struct shm
{
char buf[32];
int flag;
};
int main(int argc, char *argv[])
{
// 1.创建key
key_t key = ftok("./DoNotRemove.KEY", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%d\n", key);
// 2.创建或者打开共享内存
int shmid = shmget(key, sizeof(struct shm), IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, sizeof(struct shm), 0666);
else
{
perror("shmget err");
return -1;
}
}
// 3.映射共享内存
struct shm *p = shmat(shmid, NULL, 0);
if (NULL == p)
{
perror("shmat err");
return -1;
}
// 4.操作:循环向共享内存是输入,判断quit
p->flag = 0; //0 需要写入
while (1)
{
fgets(p->buf, 32, stdin);
p->flag = 1; //1 写入完成
if (strcmp("quit\n", p->buf) == 0)
break;
}
// 5.用完撤销映射
shmdt(p);
// 6.删除共享内存
// 写者不能删,读者才能删除
return 0;
}
out:
// 两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
struct shm
{
char buf[32];
int flag;
};
int main(int argc, char *argv[])
{
// 1.创建key
key_t key = ftok("./DoNotRemove.KEY", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%d\n", key);
// 2.创建或者打开共享内存
int shmid = shmget(key, sizeof(struct shm), IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, sizeof(struct shm), 0666);
else
{
perror("shmget err");
return -1;
}
}
// 3.映射共享内存
struct shm *p = shmat(shmid, NULL, 0);
if (NULL == p)
{
perror("shmat err");
return -1;
}
// 4.操作:循环向共享内存 读取,判断quit
while (1)
{
if (p->flag == 1)
{
if (strcmp("quit\n", p->buf) == 0)
break;
else
{
printf("buf:%s\n", p->buf);
p->flag = 0;
}
}
}
// 5.用完撤销映射
shmdt(p);
// 6.删除共享内存
// 写者不能删,读者才能删除
return 0;
}
10. 共享内存+信号灯集
10.1. 两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束(共享内存+信号灯集)
in:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#define BuffSize 128
union semun {
int val;
};
// 初始化,信号灯集
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
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;
char *shmBuff = NULL;
key = ftok("./DoNotRemove.KEY", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建/打开共享内存
shmid = shmget(key, BuffSize, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0)
{
if (errno == EEXIST)
shmid = shmget(key, BuffSize, 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);
}
printf("semid:%d\n", semid);
//映射
shmBuff = (char *)shmat(shmid, NULL, 0);
if (shmBuff == (char *)-1)
{
perror("shmat err");
return -1;
}
while (1)
{
// 获取数据
fgets(shmBuff, BuffSize, stdin);
if (shmBuff[strlen(shmBuff) - 1] == '\n')
shmBuff[strlen(shmBuff) - 1] = '\0';
//释放资源
sem_op(semid, 0, 1);
if (0 == strcmp(shmBuff, "quit"))
break;
}
// 取消 共享内存的映射
shmdt(shmBuff);
// 删除信号灯集
// 让读进程去删除
return 0;
}
out:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#define BuffSize 128
union semun {
int val;
};
// 初始化,信号灯集
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
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;
char *shmBuff = NULL;
key = ftok("./DoNotRemove.KEY", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
//创建/打开共享内存
shmid = shmget(key, BuffSize, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0)
{
if (errno == EEXIST)
shmid = shmget(key, BuffSize, 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);
}
printf("semid:%d\n", semid);
//映射
shmBuff = (char *)shmat(shmid, NULL, 0);
if (shmBuff == (char *)-1)
{
perror("shmat err");
return -1;
}
while (1)
{
//申请资源
sem_op(semid, 0, -1);
if (0 == strcmp(shmBuff, "quit"))
break;
else
printf("shm:%s\n", shmBuff);
}
// 取消 共享内存的映射
shmdt(shmBuff);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
// 删除灯集
semctl(semid, 0, IPC_RMID); //第二个参数为0即可,不必深究
return 0;
}
10.2. 一个进程对共享内存存放数据"welcome to hqyj"循环倒置,一个进程循环输出共享内存的内容。
转置:
// 进程间通信 - 共享内存结合信号灯集的使用
// 练习:一个进程对共享内存存放数据"welcome to hqyj"循环倒置,
// 一个进程循环输出共享内存的内容。
// 共享内存: ftok shmget shmat shmdt shmctl
// 信号灯集 semget semop semctl
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semun {
int val;
};
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
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 *argv[])
{
// 1.创建key
key_t key = ftok(".", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%d\n", key);
// 2.创建或者打开共享内存
int 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;
}
}
// 3.映射共享内存
char *p = shmat(shmid, NULL, 0);
if (p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 赋值
strcpy(p, "welcome to hqyj");
// 4.创建或者打开信号灯集合
int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == 17)
semid = semget(key, 1, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{
// 只在第一次创建成功后进行初始化
// 刚开始没资源,只有编号0的灯
seminit(semid, 0, 0);
}
printf("semid:%d\n", semid);
int count = 0;
while (1)
{
// 进行倒置
int len = strlen(p);
for (int i = 0; i < len / 2; i++)
{
char temp = p[i];
p[i] = p[len - i - 1];
p[len - i - 1] = temp;
}
printf("inverst: %d\n", ++count);
// 释放资源 +1
sem_op(semid, 0, 1);
// sleep(1);
}
// 撤销映射 共享内存
shmdt(p);
// 删除 共享内存
shmctl(shmid, IPC_RMID, NULL);
// 删除 信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
打印:
// 进程间通信 - 共享内存结合信号灯集的使用
// 练习:一个进程对共享内存存放数据"welcome to hqyj"循环倒置,
// 一个进程循环输出共享内存的内容。
// 共享内存: ftok shmget shmat shmdt shmctl
// 信号灯集 semget semop semctl
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semun {
int val;
};
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
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 *argv[])
{
// 1.创建key
key_t key = ftok(".", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%d\n", key);
// 2.创建或者打开共享内存
int 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;
}
}
// 3.映射共享内存
char *p = shmat(shmid, NULL, 0);
if (p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 4.创建或者打开信号灯集合
int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == 17)
semid = semget(key, 1, 0666);
else
{
perror("semget err");
return -1;
}
}
else
{
// 只在第一次创建成功后进行初始化
// 刚开始没资源,只有编号0的灯
seminit(semid, 0, 0);
}
printf("semid:%d\n", semid);
while (1)
{
// 没有资源就等待
sem_op(semid, 0, -1);
printf("buf:%s\n", p);
}
// 撤销映射 共享内存
shmdt(p);
// 删除 共享内存
shmctl(shmid, IPC_RMID, NULL);
// 删除 信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
11. 对比IPC三种方式的创建步骤
共享内存 | 信号灯集 | 消息队列 | 注意问题 |
|
|
| 信号灯集和消息队列,都有创建返回ID为0而不能正常使用的问题,是系统导致的。 |
12. C语言练习题
12.1. 单词转置
实现:
my name is EngSong 转为 EngSong is name my
#include <stdio.h>
#include <stdlib.h>
void swap(char *head, char *tail);
void fun(char *sp);
int main(int argc, char const *argv[])
{
char ch[32] = "my name is TestText";
swap(ch, ch + strlen(ch) - 1);
fun(ch);
printf("ch=%s\n", ch);
return 0;
}
void swap(char *head, char *tail)
{
while (head < tail)
{
*head ^= *tail;
*tail ^= *head;
*head++ ^= *tail--; //*head ^= *tail head++ tail--
}
}
void fun(char *sp)
{
char *head = NULL; //单词地一个字符地址
while (*sp)
{
head = sp;
while (*sp != ' ' && *sp != '\0')
sp++;
swap(head, sp - 1);
if (*sp != '\0')
sp++;
}
}