0.1【练习题 + 其他知识点】

1. 其他知识点

1.1. man 手册

1.2. ctags追踪

ctags索引:

  1. vi -t 要查找的内容,(查找宏,数据类型等)
    输入前面序号,回车
  2. 继续追踪
    将光标定位到要追踪的内容上,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),父进程从文件开始到文件的一半开始拷贝,子进程从文件的一半到文件末尾。

提示:

  1. 长度获取:lseek() 400-->200
  2. 定位到一半lseek
  3. buf[32] 200-32-32 read
  4. 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结束程序。

分析:

  1. 全局变量
  2. 全局变量--->标志位
    1. 可输出状态(输入完成)
    2. 不可输出状态(打印完成)

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功能

  1. 通过两个代码,第一个in,第二个out
  2. 由有名管道特性,单个进程只打开 读或者写 会阻塞,直到另一端打开 对应的 写或者读。
  3. 并且 有名管道,自带流量控制,不用担心读写过程
#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. 用信号的知识实现司机和售票员问题。

  1. 售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
  2. 售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
  3. 司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
  4. 司机等待售票员下车,之后司机再下车。

思路:父子进程:

  • 父进程:司机:
    • 忽略: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三种方式的创建步骤

共享内存

信号灯集

消息队列

注意问题

  1. 创建key值 ftok
  2. 创建或打开共享内存 shmget
  3. 映射共享内存到用户空间(拿地址)shmat
  4. 撤销映射 shmdt
  5. 删除共享内存 shmctl
  1. 创建key值 ftok
  2. 创建或打开信号灯集 semget
  3. 初始化信号灯 semctl
  4. pv操作 semop
  5. 删除信号灯集 semctl
  1. 创建key值 ftok
  2. 创建或打开消息队列  msgget
  3. 添加消息 :按照消息类型将消息添加到消息队列末尾  msgsnd
  4. 读取消息 :按照类型将消息从消息队列中读走 msgrcv
  5. 删除消息队列 msgctl

信号灯集和消息队列,都有创建返回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++;
    }
}
  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值