Linux系统编程

文章目录

  • Linux文件开发
    • 查看Linux用户手册
    • 文件的打开与创建open函数
      • 包含头文件
      • 函数描述
      • 查看文件权限
      • 用法
    • 文件写入操作编程write函数
      • write函数描述
      • 包含头文件
      • close函数
      • 用法
    • 文件读取操作read函数
      • 函数描述
      • 包含头文件
      • 用法
    • 文件光标移动操作lseek函数
      • 函数描述
      • 包含头文件
      • 用法
    • 文件打开创建补充
      • O_EXCL参数描述
      • O_EXCL参数的用法
      • O_APPEND参数描述
      • O_APPEND参数的用法
      • O_TRUNC参数描述
      • O_TRUNC参数的用法
      • creat函数描述
      • creat函数的用法
    • 文件操作原理简述
      • 文件描述符
        • 标准输入和标准输出
      • 操作文件的步骤
    • 文件操作应用——实现cp命令
      • main函数的参数
      • 编程实现
    • perror()函数打印错误信息
    • 文件编程应用——修改程序的配置文件
    • 写除了字符串类型到文件
      • 写一个整数到文件
      • 写一个结构体到文件
      • 写一个结构体数组到文件
    • open和fopen的区别
    • 标准C库操作文件
      • fopen()函数描述
      • fwrite()和fread()函数描述
      • fseek()函数描述
      • 用法
      • 写入结构体
      • fputc()函数描述
      • 用法
      • fgetc()和feof()函数描述
      • 用法
  • Linux进程
    • 进程的相关概念
      • 什么是程序,什么是进程,有什么区别?
      • 如何查看系统中有哪些进程?
      • 什么是进程标识符?
      • 什么叫父进程,什么叫子进程?
      • C程序的存储空间是如何分配的?
    • 创建进程fork()函数
      • 函数描述
      • 用法
      • 进程创建发生了什么
      • 创建新进程的实际运用场景
        • 目的一
        • 目的二
      • fork总结
    • 创建进程vfork()函数
      • vfork()和fork()函数的区别
    • 进程退出
    • 父进程等待子进程退出
      • 为什么父进程要等待子进程退出
      • 子进程退出状态不被收集会变成僵尸进程
      • 父进程等待子进程退出并收集退出状态
        • 用wait()函数收集子进程退出的状态
        • wait()函数
        • waitpid()和wait()函数的区别
    • 孤儿进程
    • exec族函数
      • exec族函数的作用
      • exec族函数描述
      • exec族函数的用法
        • 带l的execl()函数的用法
        • 带p的execlp()函数
        • 带v不带l的函数execv、execvp、execve
    • system()函数
    • popen()函数
  • 单机进程间通信
    • 进程间通信原理
      • 管道(无名管道)pipe
      • 命名管道FIFO
      • 消息队列
      • 共享内存
      • 信号
      • 信号量
    • 无名管道编程
    • 命名管道FIFO编程
    • 消息队列编程
      • 单向发送与接收消息
      • 互相发送和接收消息
      • 生成键值
      • 删除消息队列
    • 共享内存编程
    • 信号编程
      • 初级信号编程
      • 高级信号编程
    • 信号量编程
    • 消息队列、共享内存、信号量综合
    • 同步、互斥、信号量初值的设定、并发和并行
    • 五种通信方式总结
  • 线程
    • 线程概述
      • 进程与线程的区别
      • 使用线程的理由
      • 异步
      • 线程同步和互斥
    • 线程编程
      • 创建线程pthread_create和pthread_self
      • 阻塞主线程
      • 线程共享内存空间的代码验证
      • 线程同步之互斥锁加锁解锁
      • 对共享内存的加锁和解锁
      • 死锁
      • 线程的条件控制实现线程的同步
      • 线程同步——生产者消费者问题
        • 生产者消费者问题
        • 用互斥锁编程实现
  • 网络编程
    • 网络编程概述
      • 字节序
      • socket编程步骤
    • socket网络编程实战
      • 寻找关键字在哪个库里
      • 网络编程实战

Linux文件开发

查看Linux用户手册

man + 关键字
man 2 open打开用户手册中的第2页的open函数那一章

文件的打开与创建open函数

包含头文件

#include 
#include 
#include 

函数描述

Linux系统编程_第1张图片
file descriptor文件描述符
call调用
给一个文件的路径,返回一个文件描述符,它是一个小的非负整数用在后续的系统调用中。文件打开成功返回非负整数,打开失败返回-1
Linux系统编程_第2张图片

查看文件权限

ls -lrw可读写;x可执行
在这里插入图片描述
open函数中的mode权限:
Linux系统编程_第3张图片
可读可写就是0400+0200=0600

用法

  1. 正常打开一个文件:新建一个file1文件,在当前文件夹内新建file1.c输入如下代码
  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     int fd;
  9     fd = open("./file1",O_RDWR);
 10     printf("fd = %d\n",fd);
 11     return 0;
 12 }
//fd = 3
  1. 如果文件不存在则创建文件并打开:删除file1文件,在当前文件夹内新建file1.c输入如下代码
 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     int fd;
  9     fd = open("./file1",O_RDWR);
 10     if(fd = -1)
 11     {
 12         printf("file doesn't exist!\n");
 13         fd = open("./file1",O_RDWR|O_CREAT,00600);
 14         if(fd > 0)
 15         {
 16             printf("create file successfully!\n");
 17         }
 18     }
 19     printf("fd = %d\n",fd);
 20     return 0;
 21 }
//file doesn't exist!
//create file successfully!
//fd = 3

文件写入操作编程write函数

write函数描述

Linux系统编程_第4张图片
write函数从buf缓冲区向文件描述符fd指代的文件中写入count大小的数据。
如果写入成功,返回写入数据的大小(0代表没东西被写入),-1表示有错误。

包含头文件

#include 

close函数

在这里插入图片描述
写入文件内容后用close函数关闭文件

用法

如果file1文件不存在则先创建,再写入12345678901234567890,最后关闭文件file1

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR);
 12     if(fd = -1)
 13     {
 14         printf("file doesn't exist!\n");
 15         fd = open("./file1",O_RDWR|O_CREAT,00600);
 16         if(fd > 0)
 17         {
 18             printf("create file successfully!\n");
 19         }
 20     }
 21     printf("open file successfully!\n",fd);
 22     char *buf = "12345678901234567890";
 23     write(fd,buf,strlen(buf)*sizeof(char));
 24     close(fd);
 25     return 0;
 26 }

文件读取操作read函数

函数描述

Linux系统编程_第5张图片
函数会从文件描述符fd所在的文件中读取count个字节大小的数据到buf缓冲区内。
读取成功返回读取的字节大小,失败返回-1

包含头文件

#include 

用法

读取写入file1文件的数据,读取时采用重新打开文件让光标定位到数据开头

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 
  9 int main()
 10 {
 11     int fd;
 12     fd = open("./file1",O_RDWR);
 13     if(fd = -1)
 14     {
 15         printf("file doesn't exist!\n");
 16         fd = open("./file1",O_RDWR|O_CREAT,00600);
 17         if(fd > 0)
 18         {
 19             printf("create file successfully!\n");
 20         }
 21     }
 22     printf("open file successfully!\n",fd);
 23     char *buf = "12345678901234567890";
 24     int n_write;
 25     n_write = write(fd,buf,strlen(buf)*sizeof(char));
 26     close(fd);
 27     fd = open("./file1",O_RDWR|O_CREAT,00600);
 28     if(n_write != -1)
 29     {
 30         printf("write %d bytes to file1\n",n_write);
 31     }
 32     int n_read;
 33     char *readBuf = (char *)malloc(n_write);
 34     n_read = read(fd,readBuf,n_write);
 35     printf("read %d bytes,context is:%s\n",n_read,readBuf);
 36     return 0;
 37 }

文件光标移动操作lseek函数

函数描述

Linux系统编程_第6张图片
函数是用来重新定位打开的文件的光标的。
SEEK_SET光标在数据头部位置
SEEK_CUR光标在数据当前位置
SEEK_END光标在数据尾部位置
offset设置光标向右偏移字节大小,向左偏移为负数
Linux系统编程_第7张图片
读取成功返回从文件开始的偏移字节大小,读取失败返回-1

包含头文件

#include 
#include 

用法

  1. 打开文件并写入数据后,将光标重新定位至数据头部,读取数据
  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 
  9 int main()
 10 {
 11     int fd;
 12     fd = open("./file1",O_RDWR);
 13     if(fd = -1)
 14     {
 15         printf("file doesn't exist!\n");
 16         fd = open("./file1",O_RDWR|O_CREAT,00600);
 17         if(fd > 0)
 18         {
 19             printf("create file successfully!\n");
 20         }
 21     }
 22     printf("open file successfully!\n",fd);
 23     char *buf = "12345678901234567890";
 24     int n_write;
 25     n_write = write(fd,buf,strlen(buf)*sizeof(char));
 26     if(n_write != -1)
 27     {
 28         printf("write %d bytes to file1\n",n_write);
 29     }
 30     int n_read;
 31     char *readBuf = (char *)malloc(n_write);
 32     lseek(fd,-n_write,SEEK_END);
 //lseek(fd,0,SEEK_SET); //lseek(fd,-n_write,SEEK_CUR);都可以
 33     n_read = read(fd,readBuf,n_write);
 34     printf("read %d bytes,context is:%s\n",n_read,readBuf);
 35     return 0;
 36 }
  1. 计算文件大小
1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR);
 12     if(fd == -1)
 13     {
 14         printf("file1 doesn't exist!\n");
 15         fd = open("./file1",O_RDWR|O_CREAT,00600);
 16         printf("create file1 success!\n");
 17     }
 18     printf("open file1 success!");
 19     int fileSize;
 20     fileSize = lseek(fd,0,SEEK_END);
 21     printf("file1's size is %d\n",fileSize);
 22     return 0;
 23 }

因为lseek返回的是从文件开始到光标的偏移字节大小,所以要把光标移动到文件尾部lseek(fd,0,SEEK_END)

文件打开创建补充

Linux系统编程_第8张图片

O_EXCL参数描述

如果在open函数内同时写了O_CREAT,如果文件存在则打开失败返回-1

O_EXCL参数的用法

如果文件file1存在则提示文件已存在,如果删除file1重新调用程序,则创建文件

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     int fd;
  9     fd = open("./file1",O_RDWR|O_CREAT|O_EXCL,00600);
 10     if(fd == -1)
 11     {
 12         printf("file1 has existed!\n");
 13     }
 14     return 0;
 15 }

O_APPEND参数描述

写入数据时如果首次调用含O_APPEND的open函数则会在文件尾部写入数据

O_APPEND参数的用法

如果用了该参数则在文件尾部写数据,如果没用该参数,写入的新数据会覆盖原来的位置

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR|O_APPEND);
 12     printf("fd = %d\n",fd);
 13     char *buf = "peterpeter";
 14     write(fd,buf,strlen(buf)*sizeof(char));
 15     close(fd);
 16     return 0;
 17 }

O_TRUNC参数描述

如果文件中有内容,将其清空再写入新数据。
注意:在open函数里加了该参数,后面就读取不到内容了,因为被清空了

O_TRUNC参数的用法

 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR|O_TRUNC);
 12     printf("fd = %d\n",fd);
 13     char *buf = "peterpeter";
 14     write(fd,buf,strlen(buf)*sizeof(char));
 15     close(fd);
 16     return 0;
 17 }

creat函数描述

Linux系统编程_第9张图片

creat函数的用法

在当前路径下创建一个名为file2的文件,可读可写可执行

 1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     creat("./file2",S_IRWXU);
  8     return 0;
  9 }

文件操作原理简述

文件描述符

Linux系统编程_第10张图片

标准输入和标准输出

文件描述符(0,1,2)宏STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO代表标准输入、标准输出和标准错误。标准输入就是从键盘读取;标准输出就是从控制台输出

  1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     char *readBuf = (char *)malloc(10);
  8     read(STDIN_FILENO,readBuf,10);
  9     write(STDOUT_FILENO,readBuf,10);
 10     printf("done!\n");
 11     return 0;
 12 }   
从键盘输入//sdfs
//sdfs
//done!

从键盘读取10个字节到readBuf缓冲区,再从readBuf缓冲区写到控制台10个字节,所以这里都是readBuf

操作文件的步骤

Linux系统编程_第11张图片
静态文件存在块设备中 -> 调用open -> 在linux内核申请内存(动态内存)用一个数据结构储存 -> 读写 -> 调用close -> linux内核中的动态内存更新块设备中的静态文件

文件操作应用——实现cp命令

main函数的参数

main函数的参数从第一个指令开始

  1 #include 
  2 
  3 int main(int argc,char **argv)
  4 {
  5     printf("number of argc is:%d\n",argc);
  6     printf("No.1 param is:%s\n",argv[0]);
  7     printf("No.2 param is:%s\n",argv[1]);
  8     printf("No.3 param is:%s\n",argv[2]);
  9     return 0;
 10 }

在这里插入图片描述

编程实现

思路:
打开src.c
读src.c中的数据到readBuf
打开/创建des.c
将readBuf写入des.c,如果des.c不存在则创建,如果des.c存在,写入前清空其内容
关闭两个文件

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 
  9 int main(int argc,char **argv)
 10 {
 11     if(argc != 3)
 12     {
 13         printf("param error!\n");
 14         exit(-1);
 15     }
 16     int fdSrc;
 17     fdSrc = open(argv[1],O_RDWR);
 18     int fileSize = lseek(fdSrc,0,SEEK_END);
 19     lseek(fdSrc,0,SEEK_SET);
 20     char *readBuf = (char *)malloc(sizeof(char)*fileSize);
 21     read(fdSrc,readBuf,fileSize);
 22     int fdDes;
 23     fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,00600);
 24     write(fdDes,readBuf,strlen(readBuf)*sizeof(char));
 25     close(fdSrc);
 26     close(fdDes);
 27     return 0;
 28 }
 //gcc mycp.c -o mycp
//./mycp file1 file2
//file1被复制成file2

perror()函数打印错误信息

调用perror函数后程序会自动结束

文件编程应用——修改程序的配置文件

思路:
打开文件
读取文件到readBuf缓冲区
用strstr()查找目标字符串
指针定位到目标数据位置并修改数据
将readBuf写入原文件
关闭文件
以下代码只能修改1位数字

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 
  9 int main(int argc,char **argv)
 10 {
 11     if(argc != 2)
 12     {
 13         printf("param error!\n");
 14         exit(-1);
 15     }
 16     int fd;
 17     fd = open(argv[1],O_RDWR);
 18     int fileSize = lseek(fd,0,SEEK_END);
 19     lseek(fd,0,SEEK_SET);
 20     char *readBuf = (char *)malloc(sizeof(char)*fileSize + 1);
 21     read(fd,readBuf,fileSize);
 22     char *p = strstr(readBuf,"WIDTH = ");
 23     if(p == NULL)
 24     {
 25         printf("not found!\n");
 26         exit(-1);
 27     }
 28     p = p + strlen("WIDTH = ");
 29     *p = '8';
 30 /*  while(++p != NULL)
 31     {
 32         if(*p == '\0')
 33         {
 34             break;
 35         }
 36         *p = '\0';
 37     }*/
 38     lseek(fd,0,SEEK_SET);
 39     write(fd,readBuf,fileSize);
 40     close(fd);
 41     return 0;
 42 }
 //gcc changeConfig.c -o changeConfig
 //./changeConfig test.config
 WIDTH修改为8

写除了字符串类型到文件

写一个整数到文件

write()和read()函数中的参数类型是const void * 所以不一定要是char * 类型

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 int main()
  8 {
  9     int fd;
 10     fd = open("./file1",O_RDWR);
 11     int data = 10;
 12     write(fd,&data,sizeof(int));
 13     lseek(fd,0,SEEK_SET);
 14     int data2;
 15     read(fd,&data2,sizeof(int));
 16     printf("read:%d\n",data2);
 17     close(fd);
 18     return 0;
 19 }

写一个结构体到文件

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 typedef struct node
  8 {
  9     int a;
 10     char c;
 11 }Node;
 12 
 13 int main()
 14 {
 15     int fd;
 16     fd = open("./file1",O_RDWR);
 17     Node node1 = {1,'g'};
 18     write(fd,&node1,sizeof(Node));
 19     lseek(fd,0,SEEK_SET);
 20     Node node2;
 21     read(fd,&node2,sizeof(Node));
 22     printf("read:%d,%c\n",node2.a,node2.c);
 23     close(fd);
 24     return 0;
 25 }

写一个结构体数组到文件

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 typedef struct node
  8 {
  9     int a;
 10     char c;
 11 }Node;
 12 
 13 int main()
 14 {
 15     int fd;
 16     fd = open("./file1",O_RDWR);
 17     Node node1[2] = {{1,'g'},{2,'h'}};
 18     write(fd,&node1,sizeof(Node)*2);
 19     lseek(fd,0,SEEK_SET);
 20     Node node2[2];
 21     read(fd,&node2,sizeof(Node)*2);
 22     printf("read:%d,%c\n",node2[0].a,node2[0].c);
 23     printf("read:%d,%c\n",node2[1].a,node2[1].c);
 24     close(fd);
 25     return 0;
 26 }

open和fopen的区别

总结open与fopen的区别

  1. 来源不同
    open是Unix系统调用函数。fopen的C语言库函数。

  2. 移植性不同
    fopen移植性更好,能在更多系统中调用。

  3. 适用范围不同
    fopen用来操作普通文件。open主要用于linux系统。

  4. 缓冲
    fopen是在缓冲区操作文件,效率更高。open是通过用户态和内核态切换操作文件。但现在的机器这点效率忽略不计。

标准C库操作文件

fopen()函数描述

在这里插入图片描述
Linux系统编程_第12张图片
在这里插入图片描述
fopen()函数用来打开文件。传入path文件路径,mode打开的文件权限,mode是字符串指针 。权限如第二张图,w+表示不存在就创建文件。成功调用该函数返回FILE的指针,有错返回NULL

fwrite()和fread()函数描述

Linux系统编程_第13张图片
fwrite()函数用来向文件写入数据。它从ptr缓冲区获取nmemb数量每个size字节大小的数据写入stream指向的文件流。

fread()函数用来读取数据。它从stream指向的文件流中读取nmemb数量每个size字节大小的数据储存到ptr缓冲区中。

fwrite()和fread()返回成功写入或读取的项目数量,如果调用出错返回很小的项目数量或0。它们的返回值取决于第三个参数nmemb
如果fwrite的nmemb参数写的很大,会将乱码写入目标文件,如下图:
在这里插入图片描述
如果fread的nmemb参数写的很大,没有问题

fseek()函数描述

Linux系统编程_第14张图片
在这里插入图片描述
fseek用来操作文件中的光标位置。参数和lseek用法相同。成功调用返回0,失败返回-1。

用法

  1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     FILE *fp;
  8     fp = fopen("./file1","w+");
  9     char *str = "hello world!";
 10     int n_write = fwrite(str,sizeof(char),strlen(str),fp);
 11     fseek(fp,0,SEEK_SET);
 12     char *readBuf = (char *)malloc(20);
 13     int n_read = fread(readBuf,sizeof(char),strlen(str),fp);
 14     printf("read:%s\n",readBuf);
 15     printf("n_write = %d\n",n_write);
 16     printf("n_read = %d\n",n_read);
 17     return 0;
 18 }
//read:hello world!
//n_write = 12
//n_read = 12

如果写入的数据数量大于字符数量,比如fwrite(str,sizeof(char),20,fp);会在file1中多写入其它字符造成错误

写入结构体

  1 #include 
  2 #include 
  3 #include 
  4 
  5 typedef struct Node
  6 {
  7     int num;
  8     char name;
  9 }Node;
 10 int main()
 11 {
 12     Node data1 = {100,'a'};
 13     FILE *fp;
 14     fp = fopen("./file1","w+");
 15     int nwrite = fwrite(&data1,sizeof(Node),1,fp);
 16     Node data2;
 17     fseek(fp,0,SEEK_SET);
 18     int nread = fread(&data2,sizeof(Node),1,fp);
 19     printf("read:num = %d\tname = %c\n",data2.num,data2.name);
 20     printf("write:%d\tread:%d\n",nwrite,nread);
 21     fclose(fp);
 22     return 0;
 23 }
//read:num = 100    name = a
//write:1   read:1

fputc()函数描述

在这里插入图片描述
在这里插入图片描述
Linux系统编程_第15张图片
fputc()函数将参数c强制转换成无符号字符,写入stream文件流
返回被写入的无符号字符强转成整型的整数,失败返回EOF

用法

将字符串写入file1文件中

  1 #include 
  2 #include 
  3 
  4 int main()
  5 {
  6     FILE *fp;
  7     fp = fopen("./file1","w+");
  8     char *str = "hello world!";
  9     int i;
 10     int len = strlen(str);
 11     int a;
 12     for(i = 0; i < len; i++)
 13     {
 14         a = fputc(*str,fp);
 15         str++;
 16         printf("fputc = %d\n",a);
 17     }
 18     fclose(fp);
 19     return 0;
 20 }
注意这里for循环中要将len提前赋值,因为如果写成i < strlen(str),随着每次循环str++,strlen(str)的值也会随之变化

fgetc()和feof()函数描述

在这里插入图片描述
在这里插入图片描述
fgetc()函数从stream文件流中读取一个字符,将其转换成无符号字符并强转成整型输出,返回EOF时有错误
Linux系统编程_第16张图片
在这里插入图片描述
feof()函数判断光标是否在文件尾,如果光标在文件尾部返回非零数,如果不在返回0

用法

  1 #include 
  2 #include 
  3 
  4 int main()
  5 {
  6     FILE *fp;
  7     fp = fopen("./file1","r");
  8     char c;
  9     while(!feof(fp))
 10     {
 11         c = fgetc(fp);
 12         printf("%c",c);
 13     }   
 14     fclose(fp);
 15     return 0;
 16 }   
//hello world!

Linux进程

进程的相关概念

什么是程序,什么是进程,有什么区别?

程序是静态的概念,gcc xxx.c -o pro,生成的pro就是程序,各种桌面应用程序没打开前都是程序。
进程是程序的一次运行活动,就是程序跑起来了,系统中就多了一个进程。

如何查看系统中有哪些进程?

在Windows中通过任务管理器查看进程。

在Linux中使用ps指令查看,ps -aux查看所有进程,ps -aux|grep init在所有进程中查看带init字段的进程。
top指令查看进程排名,可以查看cpu消耗率,内存占有率等。

什么是进程标识符?

每个进程都有一个非负整数表示唯一ID,叫做pid,类似身份证。
pid = 0:称为交换进程(swapper)作用是进程调度
pid = 1:init进程,作用是系统初始化
编程调用getpid()函数能获取自身的进程标识符,getppid获取父进程的进程标识符。

 1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid = getpid();
  9     printf("my pid = %d\n",pid);
 10     while(1);
 11     return 0;
 12 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0m7X8Wuo-1610010410777)(en-resource://database/657:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W1GE1g1r-1610010410778)(en-resource://database/659:1)]

什么叫父进程,什么叫子进程?

如果进程A创建了进程B,A叫做父进程,B叫做子进程。

C程序的存储空间是如何分配的?

代码段:算法类的代码
数据段:初始化过的变量
bss()段:在函数外未被初始化的变量
堆:calloc或malloc申请的内存
栈:局部变量储存的地方
命令行参数和环境变量:argc和argv存放的地方
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjmrzcCI-1610010410780)(en-resource://database/661:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7796BfEi-1610010410781)(en-resource://database/663:1)]

创建进程fork()函数

函数描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1bSPD8vS-1610010410782)(en-resource://database/665:1)]
fork()函数以及后面的代码会在父进程中执行一次,在子进程中再执行一次;当fork()函数返回0代表当前是子进程,返回非负数(创建的子进程ID)代表当前是父进程,调用失败返回-1

用法

fork()函数以及后面的代码会在父进程中执行一次,在子进程中再执行一次

1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid = getpid();
  9     printf("before fork,pid = %d\n",pid);
 10     fork();
 11     printf("after fork,pid = %d\n",getpid());
 12     return 0;
 13 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lv75CapE-1610010410784)(en-resource://database/667:1)]
当fork()函数返回0代表当前是子进程,返回非负数代表当前是父进程

  1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid_t forkReturn;
  9     pid = getpid();
 10     printf("before fork,pid = %d\n",pid);
 11     forkReturn = fork();
 12     printf("after fork,pid = %d\n",getpid());
 13     if(forkReturn > 0)
 14     {
 15         printf("fork return:%d\tthis is father fork,pid = %d\n",forkReturn,getpid());
 16     }
 17     else if(forkReturn == 0)
 18     {
 19         printf("fork return:%d\tthis is child fork,pid = %d\n",forkReturn,getpid());
 20     }
 21     else
 22     {
 23         printf("error\n");
 24     }
 25     return 0;
 26 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7VFomnpK-1610010410785)(en-resource://database/668:1)]

进程创建发生了什么

调用fork()函数后最早期的Linux内核进行了全拷贝:把父进程的正文、堆、栈等内存空间全部拷贝了一份给子进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9taGpbh1-1610010410786)(en-resource://database/669:1)]
后来Linux发展, 用的是写时拷贝,只对变化的部分进行拷贝

如下例:只对变量data的值进行了拷贝

 1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid_t forkReturn;
  9     pid = getpid();
 10     int data = 10;
 11     printf("before fork,pid = %d\n",pid);
 12     forkReturn = fork();
 13     printf("after fork,pid = %d\n",getpid());
 14     if(forkReturn > 0)
 15     {
 16         printf("fork return:%d,this is father fork,pid = %d,data = %d\n",forkReturn,getpid(),data);
 17     }
 18     else if(forkReturn == 0)
 19     {
 20         data += 10;
 21         printf("fork return:%d,this is child fork,pid = %d,data = %d\n",forkReturn,getpid(),data);
 22     }
 23     else
 24     {
 25         printf("error\n");
 26     }
 27     return 0;
 28 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zayYWNV3-1610010410788)(en-resource://database/670:1)]

创建新进程的实际运用场景

目的一

让父、子进程同时执行不同的。在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程继续等待下一个服务请求到达。

如下例:用data==1时模拟请求到达时

  1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     pid_t forkReturn;
  8     int data = 0;
  9     while(1)
 10     {
 11         printf("Please input data:");
 12         scanf("%d",&data);
 13         if(data == 1)
 14         {
 15             forkReturn = fork();
 16             if(forkReturn > 0)
 17             {
 18 
 19             }
 20             else if(forkReturn == 0)
 21             {
 22                 while(1)
 23                 {
 24                     printf("Do net request.pid = %d\n",getpid());
 25                     sleep(3);
 26                 }
 27             }
 28             else
 29             {
 30                 printf("error\n");
 31             }
 32         }
 33         else
 34         {
 35             printf("do nothing\n");
 36         }
 37     }
 38     return 0;
 39 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxIJSXaM-1610010410789)(en-resource://database/672:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oMJqRHPq-1610010410791)(en-resource://database/674:1)]
可以看到,除了test的父进程外输入了2次1,就模拟了有2次请求到达,创建了2个子进程去处理请求

目的二

一个进程要执行一个不同的程序,子进程从fork返回后立即调用exec。

案例需求:当父进程检测到输入1的时候,创建子进程把配置文件的字段值修改掉。

  1 #include 
  2 #include 
  3 
  4 int main()
  5 {
  6     int number = 0;
  7     int forkReturn = 0;
  8     while(1)
  9     {
 10         printf("Please input a number:");
 11         scanf("%d",&number);
 12         if(number == 1)
 13         {
 14             forkReturn = fork();
 15             if(forkReturn == 0)
 16             {
 17                 execl("../fileCompile/changeConfig","changeConfig","../fileCompile/file1",NULL);
 18             }
 19         }
 20     }
 21     return 0;
 22 }

注意:要操作不同目录下的执行文件操作不同目录下的文件时file1也要用绝对路径表示;
子进程运行execl()函数成功后会执行changeConfig.c代码,该代码最后有return 0,会结束子进程,所以不用exit和wait来结束子进程。

fork总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1EzyvEg-1610010410793)(en-resource://database/676:1)]

创建进程vfork()函数

vfork()和fork()函数的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8MBr9rf-1610010410794)(en-resource://database/678:1)]

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     forkReturn = vfork();
 11     if(forkReturn > 0)
 12     {
 13         while(1)
 14         {
 15             printf("Father fork,pid = %d\n",getpid());
 16             printf("data = %d\n",data);
 17             sleep(3);
 18         }
 19     }
 20     else if(forkReturn == 0)
 21     {
 22         while(1)
 23         {
 24             printf("Child fork,pid = %d\n",getpid());
 25             data++;
 26             if(data == 3)
 27             {
 28                 exit(0);
 29             }
 30             sleep(1);
 31         }
 32     }
 33     return 0;
 34 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFLGQZhj-1610010410795)(en-resource://database/680:1)]

进程退出

5种正常退出,3种异常退出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaXoqCBj-1610010410796)(en-resource://database/682:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkOhiZq5-1610010410798)(en-resource://database/684:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFCiHibc-1610010410799)(en-resource://database/686:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-riHlP3dS-1610010410801)(en-resource://database/688:1)]

父进程等待子进程退出

为什么父进程要等待子进程退出

因为要检查子进程的任务执行的情况

子进程退出状态不被收集会变成僵尸进程

父进程没有调用wait()函数收集子进程的退出状态

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     forkReturn = vfork();
 11     if(forkReturn > 0)
 12     {
 13         while(1)
 14         {
 15             printf("Father fork,pid = %d\n",getpid());
 16             printf("data = %d\n",data);
 17             sleep(3);
 18         }
 19     }
 20     else if(forkReturn == 0)
 21     {
 22         while(1)
 23         {
 24             printf("Child fork,pid = %d\n",getpid());
 25             data++;
 26             if(data == 3)
 27             {
 28                 exit(0);
 29             }
 30             sleep(1);
 31         }
 32     }
 33     return 0;
 34 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-92BR3Mwg-1610010410802)(en-resource://database/690:1)]
Z代表Zombie,即僵尸进程

父进程等待子进程退出并收集退出状态

在父进程中调用wait()函数

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     forkReturn = vfork();
 11     if(forkReturn > 0)
 12     {
 13         wait(NULL);
 14         while(1)
 15         {
 16             printf("Father fork,pid = %d\n",getpid());
 17             printf("data = %d\n",data);
 18             sleep(3);
 19         }
 20     }
 21     else if(forkReturn == 0)
 22     {
 23         while(1)
 24         {
 25             printf("Child fork,pid = %d\n",getpid());
 26             data++;
 27             if(data == 3)
 28             {
 29                 exit(0);
 30             }
 31             sleep(1);
 32         }
 33     }
 34     return 0;
 35 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGrrqkSY-1610010410803)(en-resource://database/692:1)]

用wait()函数收集子进程退出的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YggN9MTv-1610010410804)(en-resource://database/694:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DLuj6xMn-1610010410806)(en-resource://database/696:1)]

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     int status = 0;
 11     forkReturn = vfork();
 12     if(forkReturn > 0)
 13     {
 14         wait(&status);
 15         printf("child quit,child status:%d\n",WEXITSTATUS(status));
 16         while(1)
 17         {
 18             printf("Father fork,pid = %d\n",getpid());
 19             printf("data = %d\n",data);
 20             sleep(3);
 21         }
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         while(1)
 26         {
 27             printf("Child fork,pid = %d\n",getpid());
 28             data++;
 29             if(data == 3)
 30             {
 31                 exit(8);
 32             }
 33             sleep(1);
 34         }
 35     }
 36     return 0;
 37 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NPIfpF8D-1610010410807)(en-resource://database/698:1)]
exit()退出中的状态码和wait()等待中的状态码相等,子进程退出状态被收集

wait()函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0psSWgNQ-1610010410808)(en-resource://database/700:1)]

  • 父进程调用wait()函数时,如果有子进程在运行,要等子进程全部运行完,发送子进程的退出状态,等父进程中的wait()函数收集到退出状态再运行父进程的代码段

  • 如果main函数里没有开启任何子进程,此时调用wait()函数会直接报错

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     int status = 0;
 11     forkReturn = fork();
 12     if(forkReturn > 0)
 13     {
 14         wait(&status);
 15         printf("child quit,child status:%d\n",WEXITSTATUS(status));
 16         while(1)
 17         {
 18             printf("Father fork,pid = %d\n",getpid());
 19             printf("data = %d\n",data);
 20             sleep(3);
 21         }
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         while(1)
 26         {
 27             printf("Child fork,pid = %d\n",getpid());
 28             data++;
 29             if(data == 3)
 30             {
 31                 exit(8);
 32             }
 33             sleep(1);
 34         }
 35     }
 36     return 0;
 37 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ClmDOCK-1610010410809)(en-resource://database/702:1)]
调用的是fork()函数,本该父子进程交替执行,但在父进程中调用了wait()函数,所以要等子进程执行结束,父进程收集退出状态后再执行父进程的代码。

waitpid()和wait()函数的区别

waitpid()函数有一个选项可以不阻塞调用者,但是不会收集子进程的状态,子进程还是会变成僵尸进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFUSNk7a-1610010410810)(en-resource://database/704:1)]
waitpid(forkReturn,&status,WNOHANG);这里第一个参数是forkReturn,其值是fork创建的子进程ID,意思是等待其进程ID与子进程ID相等

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     int status = 0;
 11     forkReturn = fork();
 12     if(forkReturn > 0)
 13     {
 14         waitpid(forkReturn,&status,WNOHANG);
 15         printf("child quit,child status:%d\n",WEXITSTATUS(status));
 16         while(1)
 17         {
 18             printf("Father fork,pid = %d\n",getpid());
 19             printf("data = %d\n",data);
 20             sleep(3);
 21         }
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         while(1)
 26         {
 27             printf("Child fork,pid = %d\n",getpid());
 28             data++;
 29             if(data == 3)
 30             {
 31                 exit(8);
 32             }
 33             sleep(1);
 34         }
 35     }
 36     return 0;
 37 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QPgqmqOP-1610010410811)(en-resource://database/708:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LpNqQGxm-1610010410813)(en-resource://database/706:1)]

孤儿进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QmtSmJ3Q-1610010410814)(en-resource://database/710:1)]
如下例:在父进程中只执行了一行代码就结束,而子进程在无限执行,当父进程结束而子进程未结束时,init进程会收留孤儿进程,成为其父进程,即ID=1的进程

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     forkReturn = fork();
 10     if(forkReturn > 0)
 11     {
 12         printf("Father fork,pid = %d\n",getpid());
 13     }
 14     else if(forkReturn == 0)
 15     {
 16         while(1)
 17         {
 18             printf("Child fork,pid = %d,father pid = %d\n",getpid(),getppid());
 19             sleep(1);
 20         }
 21     }
 22     return 0;
 23 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b1XlJFsW-1610010410815)(en-resource://database/712:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SAPScwAV-1610010410817)(en-resource://database/714:1)]

exec族函数

参考博文

exec族函数的作用

去执行另外一个程序(可执行文件)。常在fork函数创建进城后使用,在fork函数创建的新进程中执行另一个程序

exec族函数描述

#include 
extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., 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[]);

exec函数族分别是:execl, execlp, execv, execvp,(最后两个带e的不常用)execle, execvpe

返回值:
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。

参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数中的字母含义:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件,不加p使用绝对路径
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

exec族函数的用法

带l的execl()函数的用法
#include 
int execl(const char *path, const char *arg, ...);

案例:调用当前文件夹下的echoarg可执行文件;echoarg用来打印初始参数;用perror()函数打印错误信息

  1 #include 
  2 #include 
  3 
  4 int main()
  5 {
  6     printf("before execl\n");
  7     if(execl("./bin/echoarg","echoarg","a",NULL) == -1)
  8     {
  9         printf("execl fail!\n");
 10         perror("why:");
 11     }
 12     printf("after execl\n");
 13     return 0;
 14 }

echoarg.c代码如下:

  1 #include 
  2 
  3 int main(int argc, char *argv[])
  4 {
  5     int i;
  6     for(i = 0; i < argc; i++)
  7     {
  8         printf("argv[%d] = %s\n",i,argv[i]);
  9     }
 10     return 0;
 11 }

因为执行文件的路径错误所有报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOfoc5yO-1610010410818)(en-resource://database/724:1)]
更改路径后的正常运行:

1 #include 
  2 #include 
  3 
  4 int main()
  5 {
  6     printf("before execl\n");
  7     if(execl("./echoarg","echoarg","a",NULL) == -1)
  8     {
  9         printf("execl fail!\n");
 10         perror("why:");
 11     }
 12     printf("after execl\n");
 13     return 0;
 14 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qut9a5uB-1610010410819)(en-resource://database/726:1)]
成功调用execl()函数后不执行后面的代码

带p的execlp()函数

exaclp函数带p,所以能通过环境变量PATH查找到可执行文件ps

//文件execlp.c
#include 
#include 
#include 
//函数原型:int execlp(const char *file, const char *arg, ...);
int main(void)
{
    printf("before execlp****\n");
    if(execlp("ps","ps","-l",NULL) == -1)
    {
        printf("execlp failed!\n");
    }
    printf("after execlp*****\n");
    return 0;
}
带v不带l的函数execv、execvp、execve

先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数

//文件execvp.c
#include 
#include 
#include 
//函数原型:int execvp(const char *file, char *const argv[]);

int main(void)
{
    printf("before execlp****\n");
    char *argv[] = {"ps","-l",NULL};
    if(execvp("ps",argv) == -1) 
    {
        printf("execvp failed!\n");     
    }
    printf("after execlp*****\n");
    return 0;
}

system()函数

参考博文
通过查看system()的源码发现:该函数是对execl()函数的封装,其作用也是执行其他可执行文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLFnKm3E-1610010410820)(en-resource://database/728:1)]
当system()函数执行成功还会接着执行后面的代码
需要的参数是需要敲的命令字符串,如下:system("…/fileCompile/changeConfig …/fileCompile/file1");

  1 #include 
  2 #include 
  3 #include 
  4 
  5 int main()
  6 {
  7     int number = 0;
  8     int forkReturn = 0;
  9     while(1)
 10     {
 11         printf("Please input a number:");
 12         scanf("%d",&number);
 13         if(number == 1)
 14         {
 15             forkReturn = fork();
 16             if(forkReturn == 0)
 17             {
 18                 //execl("../fileCompile/changeConfig","changeConfig","../fileCompile/file1",NULL);
 19                 system("../fileCompile/changeConfig ../fileCompile/file1");
 20             }
 21         }
 22     }
 23     return 0;
 24 }

popen()函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1VkWWNzF-1610010410822)(en-resource://database/730:1)]
参考博文
作用:
popen() 函数用于创建一个管道:其内部实现为调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程这个进程必须由 pclose() 函数关闭。功能和system()类似,但是能接收返回值

参数说明:
command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

返回值:
如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL

案例:用popen()函数执行ps指令并将ps显示的内容写到缓冲区内

  1 #include 
  2 
  3 int main()
  4 {
  5     FILE *fp;
  6     char ptr[1024] = {0};
  7     fp = popen("ps","r");
  8     int nread = fread(ptr,1,1024,fp);
  9     printf("size:%d,read:%s\n",nread,ptr);
 10     return 0;
 11 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vo9A1UTJ-1610010410823)(en-resource://database/732:1)]

单机进程间通信

一台机器上两个不同的进程之间可以发送和接收信息,就是单机进程间通信。
在两台机器上运行着两个不同的进程,他们之间的通信通过网络,属于多机通信。
进程间通信IPC(InterProcess Communication)

单机进程间通信的方式有:半双工管道、全双工管道、消息队列、信号量、共享内存
多机进程间通信的方式有:套接字、STREAMS

进程间的五种通信方式

进程间通信原理

进程间通信的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中Socket和Streams支持不同主机上的两个进程通信。

管道(无名管道)pipe

特点:

  1. 半双工,只能单向数据流动
  2. 用于亲缘关系的进程之间的通信(父子、兄弟进程之间)
  3. 可以看做是特殊的文件,可以使用read和write函数,但它只存在于内存中

命名管道FIFO

FIFO是一种文件类型。
特点:

  1. 可以在无关的进程之间通信
  2. FIFO的路径名与之相关,它以一种特殊设备文件形式存在于文件系统中

当open一个FIFO时,没有指定O_NONBLOCK(默认),只读open要阻塞到某个其它进程为写而打开此FIFO。只写open阻塞到有其它只读进程打开它。

消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

特点:

  1. 消息队列是面向记录的,其中的消息具有特定的格式(一种结构体)以及特定的优先级
  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除
  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取

共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

特点:

  1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
  2. 因为多个进程可以同时操作,所以需要进行同步。
  3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

信号

什么是信号?
进程1在执行代码,收到进程2发来的信号,进程1进行处理。

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式(软中断)

用户进程对信号的响应方式:

  1. 忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
  2. 捕捉信号(最常用的):定义信号处理函数,当信号发生时,执行相应的处理函数。
  3. 执行缺省操作:Linux对每种信号都规定了默认操作

怎么用杀死信号杀死进程?

  1. ps -aux|grep a.out查看相应进程
  2. kill -l查看信号列表
  3. kill -9 (进程的id)杀死相应进程或kill -SIGKILL (进程的id)

信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存

什么是信号量?
就像多个进程排队到一个房间里执行程序,信号量就像进门的钥匙,一个进程拿锁进房间后其它进程等待,等他出房间放回钥匙,下一个进程再拿这把钥匙进门执行他的程序。取钥匙相当于p操作,放回钥匙相当于v操作

P操作(假设信号量值为S):

  1. S-1
  2. 若S-1>=0,继续执行程序
  3. 若S-1<0,后面的程序等待

V操作:

  1. S+1
  2. 若S+1>0,继续执行程序
  3. 若S+1<=0,从该信号的等待队列中释放一个等待进程,然后再返回原进程继续执行或转进程调度

信号量值小于0,程序等待

无名管道编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hWTxVtje-1610010410824)(en-resource://database/756:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDxnZhkp-1610010410825)(en-resource://database/758:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLp9rSdX-1610010410826)(en-resource://database/760:1)]
pipe函数建立单向无名管道,需要2个fd数组,返回0成功,-1失败。注意:先创建管道再创建子进程

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 int main()
  7 {
  8     printf("father pid = %d\n",getpid());
  9     pid_t forkReturn;
 10     int pipefd[2];
 11     if(pipe(pipefd) == -1)
 12     {
 13         printf("create pipe failed!\n");
 14         exit(-1);
 15     }
 16     forkReturn = fork();
 17     if(forkReturn > 0)
 18     {
 19         close(pipefd[0]);
 20         write(pipefd[1],"hello from father",strlen("hello from father"));
 21         printf("sent by father,father pid = %d\n",getpid());
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         close(pipefd[1]);
 26         char *readBuf = (char *)malloc(128);
 27         read(pipefd[0],readBuf,128);
 28         printf("child read:%s\n",readBuf);
 29         printf("child pid = %d\n",getpid());
 30     }
 31     return 0;
 32 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wjQOrR4a-1610010410828)(en-resource://database/762:1)]

命名管道FIFO编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBjSCZe2-1610010410829)(en-resource://database/764:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wpExwJIh-1610010410830)(en-resource://database/766:1)]
mkfifo()函数建立命名管道,需要参数管道文件的命名路径和权限码,返回0成功,-1失败

1 #include 
  2 #include 
  3 
  4 int main()
  5 {
  6     if(mkfifo("./file",00600) == -1 && errno == EEXIST)
  7     {
  8         printf("create fifo failed!\n");
  9         perror("reason");
 10     }
 11     return 0;
 12 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ty51l4AB-1610010410831)(en-resource://database/768:1)]
第一次创建了file文件,第二次文件已存在

read.c

 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     if(mkfifo("./file",00600) == -1 && errno != EEXIST)
 11     {
 12         printf("create fifo failed!\n");
 13         perror("why");
 14     }
 15 
 16     int fd = open("./file",O_RDONLY);
 17     //blocked
 18     char readBuf[128];
 19     int nread = read(fd,readBuf,30);
 20     printf("read %d bytes,content:%s\n",nread,readBuf);
 21     close(fd);
 22     return 0;
 23 }

write.c

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     int fd = open("./file",O_WRONLY);
 11     char *str = "hello from fifo";
 12     write(fd,str,strlen(str));
 13     close(fd);
 14     return 0;
 15 }

运行read,程序在open文件后被堵塞,直到运行了write中的写的程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdinrVlp-1610010410832)(en-resource://database/770:1)]
打开另一个控制台运行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LCKFmbe-1610010410834)(en-resource://database/772:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ERdv4cT-1610010410835)(en-resource://database/774:1)]

连续读写
read.c

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     if(mkfifo("./file",00600) == -1 && errno != EEXIST)
 11     {
 12         printf("create fifo failed!\n");
 13         perror("why");
 14     }
 15 
 16     int fd = open("./file",O_RDONLY);
 17     //blocked
 18     char readBuf[128];
 19     while(1)
 20     {
 21         int nread = read(fd,readBuf,15);
 22         if(nread == 0)
 23         {
 24             break;
 25         }
 26         printf("read %d bytes,content:%s\n",nread,readBuf);
 27         sleep(1);
 28     }
 29     close(fd);
 30     return 0;
 31 }

write.c

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     int count = 0;
 11     int fd = open("./file",O_WRONLY);
 12     char *str = "hello from fifo";
 13     while(1)
 14     {
 15         write(fd,str,strlen(str));
 16         count++;
 17         if(count == 5)
 18         {
 19             break;
 20         }
 21     }
 22     close(fd);
 23     return 0;
 24 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVU9gfaK-1610010410836)(en-resource://database/780:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHgQbjsE-1610010410837)(en-resource://database/778:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-epkHvGL9-1610010410838)(en-resource://database/776:1)]

消息队列编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9ODuAmc-1610010410839)(en-resource://database/785:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MlHAJm0u-1610010410840)(en-resource://database/787:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2pLdTmf-1610010410841)(en-resource://database/789:1)]
在以下两种情况下,msgget将创建一个新的消息队列:

  • 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
  • key参数为IPC_PRIVATE。

如果msgflg同时指定了IPC_CREAT | IPC_EXCL,类似于open函数,函数调用失败了errno == EEXIST

函数调用正常返回消息队列的标识符(非负整数),失败返回-1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pquoq3vO-1610010410842)(en-resource://database/791:1)]

单向发送与接收消息

msgServer.c从消息队列上接收消息
根据key获取某一条消息队列
从该消息队列上获取消息

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 struct msgbuf
  7 {
  8     long mtype;       /* message type, must be > 0 */
  9     char mtext[128];    /* message data */
 10 };
 11 
 12 
 13 int main()
 14 {
 15     int msgId = msgget(0x1234,IPC_CREAT|00777);
 16     if(msgId == -1)
 17     {
 18         printf("get msg queue failed!\n");
 19     }
 20 
 21     struct msgbuf readBuf;
 22     msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
 23     printf("read from msg queue:%s\n",readBuf.mtext);
 24     return 0;
 25 }

msgClient.c向消息队列发送消息
根据key获取指定的一条消息队列
向该消息队列发送消息

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 struct msgbuf
  8 {
  9     long mtype;       /* message type, must be > 0 */
 10     char mtext[128];    /* message data */
 11 };
 12 
 13 
 14 int main()
 15 {
 16     int msgId = msgget(0x1234,IPC_CREAT|00777);
 17     if(msgId == -1)
 18     {
 19         printf("get msg queue failed!\n");
 20     }
 21 
 22     struct msgbuf sendBuf = {888,"this is msg from msg queue"};
 23     msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
 24 
 25     return 0;
 26 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7a0R1Cd-1610010410844)(en-resource://database/793:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izN8EfKS-1610010410845)(en-resource://database/795:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBYT1NkF-1610010410847)(en-resource://database/797:1)]

互相发送和接收消息

msgServer.c

 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 struct msgbuf
  8 {
  9     long mtype;       /* message type, must be > 0 */
 10     char mtext[128];    /* message data */
 11 };
 12 
 13 
 14 int main()
 15 {
 16     int msgId = msgget(0x1234,IPC_CREAT|00777);
 17     if(msgId == -1)
 18     {
 19         printf("get msg queue failed!\n");
 20     }
 21 
 22     struct msgbuf readBuf;
 23     msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
 24     printf("read from Server:%s\n",readBuf.mtext);
 25     struct msgbuf sendBuf = {999,"msg from server"};
 26     msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
 27     printf("send from server over!\n");
 28     return 0;
 29 }

msgClient.c

1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 struct msgbuf
  8 {
  9     long mtype;       /* message type, must be > 0 */
 10     char mtext[128];    /* message data */
 11 };
 12 
 13 
 14 int main()
 15 {
 16     int msgId = msgget(0x1234,IPC_CREAT|00777);
 17     if(msgId == -1)
 18     {
 19         printf("get msg queue failed!\n");
 20     }
 21 
 22     struct msgbuf sendBuf = {888,"this is msg from msg queue"};
 23     msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
 24     printf("msg send over!\n");
 25 
 26     struct msgbuf readBuf;
 27     msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),999,0);
 28     printf("client receive:%s\n",readBuf.mtext);
 29     return 0;
 30 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0DAtgG96-1610010410848)(en-resource://database/799:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XxVYQNX-1610010410849)(en-resource://database/801:1)]

生成键值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5Urj0QO-1610010410851)(en-resource://database/803:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4YehhYg9-1610010410852)(en-resource://database/805:1)]

key_t key;
key = ftok(".",1);
int msgId = msgget(key,IPC_CREAT|00777);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxhFFPO5-1610010410853)(en-resource://database/807:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Tpv1PSq-1610010410854)(en-resource://database/809:1)]
客户端和服务端的key相同,说明对同一个消息队列进行了消息的发送和获取

删除消息队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qq1HTRqA-1610010410855)(en-resource://database/811:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bz3qwsyb-1610010410857)(en-resource://database/813:1)]

msgctl(msgId,IPC_RMID,NULL);

共享内存编程

1 #include 
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1;size要是1兆的倍数
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1;addr为NULL由系统自动分配地址,flag为0为可读可写
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1;cmd为IPC_RMID销毁共享内存
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

编程思路
// 创建共享内存/有的话直接打开
// 将共享内存的地址映射给操作的进程
// 数据交换
// 释放共享内存
// 删除共享内存

shmw.c往共享内存中写入数据

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     key_t key = ftok(".",1);
 11     int shmId = shmget(key,1024*4,IPC_CREAT|00600);
 12     if(shmId == -1)
 13     {
 14         printf("shmget failed!\n");
 15         exit(-1);
 16     }
 17 
 18     char *shmaddr = shmat(shmId,0,0);
 19     printf("shmat succeessfully!\n");
 20     strcpy(shmaddr,"hello from shm");
 21 
 22     sleep(5);
 23 
 24     shmdt(shmaddr);
 25     shmctl(shmId,IPC_RMID,0);
 26 
 27     printf("quit!\n");
 28     return 0;
 29 }

shmr.c从共享内存中读取数据

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 int main()
  9 {
 10     key_t key = ftok(".",1);
 11     int shmId = shmget(key,1024*4,0);
 12     if(shmId == -1)
 13     {
 14         printf("shmget failed!\n");
 15         exit(-1);
 16     }
 17 
 18     char *shmaddr = shmat(shmId,0,0);
 19     printf("shmat succeessfully!\n");
 20     printf("data:%s\n",shmaddr);
 21 
 22     shmdt(shmaddr);
 23     printf("quit!\n");
 24     return 0;
 25 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2smkdEai-1610010410858)(en-resource://database/815:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGL6NFTJ-1610010410859)(en-resource://database/817:1)]

信号编程

初级信号编程

// signal函数将信号signum设置成handler函数作为信号的处理
sighandler_t signal(int signum, sighandler_t handler);

// 参数int为signum信号编号
typedef void (*sighandler_t)(int);

// kill()函数用来对任何进程发送信号
int kill(pid_t pid, int sig);

捕捉信号

  1 #include 
  2 #include 
  3 
  4 void handler(int signum)
  5 {
  6     switch(signum)
  7     {
  8     case 2:
  9         printf("SIGINT\n");
 10         break;
 11     case 9:
 12         printf("SIGKILL\n");
 13         break;
 14     }
 15 }
 16 
 17 int main()
 18 {
 19     signal(SIGINT,handler);
 20     signal(SIGKILL,handler);
 21     while(1);
 22     return 0;
 23 }

按下Ctrl C,程序将Ctrl C的信号SIGINT设置成了handler处理函数
SIGKILL信号不能忽略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dzILk5ag-1610010410860)(en-resource://database/821:1)]

用编程实现kill指令

  1 #include 
  2 #include 
  3 #include 
  4 
  5 int main(int argc, char **argv)
  6 {
  7     int signum;
  8     int pid;
  9     signum = atoi(argv[1]);
 10     pid = atoi(argv[2]);
 11     int ret = kill(pid,signum);
 12     if(ret == 0)
 13     {
 14         printf("kill done!\n");
 15     }
 16     return 0;
 17 }

运行第一个程序signalDemo
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWGxTr8n-1610010410861)(en-resource://database/823:1)]
ps -aux|grep signalDemo查看进程pid
执行程序杀死进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyKTL5P9-1610010410862)(en-resource://database/825:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9TDUC0fF-1610010410863)(en-resource://database/827:1)]

高级信号编程

高级信号编程区别于初级的是,高级信号编程可以在捕获信号的同时发送和接收信息

// 用于接收并捕获信号。signum:要捕获的信号;act:捕获信号的动作(包括接收信息);oldact:NULL为不保存之前的操作
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
// act的结构体原型。指定sa_handler和初级信号编程相同;必须要初始化sa_sigaction和sa_flags = SA_SIGINFO,int为捕获的信号,siginfo_t是结构体指针,void *content不为NULL,info就有信息,info可接收哪些信息见文档
struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };


// 用于发送信号。pid:目标进程号;sig:要发送的信号;value:其中含有要发送的信息
int sigqueue(pid_t pid, int sig, const union sigval value);
// value的联合体,int:发送整型,ptr:发送其它类型
union sigval {
               int   sival_int;
               void *sival_ptr;
           };

seniorSignalRCV.c

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 void handler(int signum, siginfo_t *info, void *context)
  7 {
  8     printf("get signum:%d\n",signum);
  9     if(context != NULL)
 10     {
 11         printf("get data:%d\n",info->si_int);
 12         printf("from process pid:%d\n",info->si_pid);
 13     }
 14 }
 15 int main()
 16 {
 17     printf("current process pid is:%d\n",getpid());
 18     struct sigaction act;
 19     act.sa_sigaction = handler;
 20     act.sa_flags = SA_SIGINFO;//be able to get message
 21     sigaction(SIGUSR1,&act,NULL);
 22     while(1);
 23     return 0;
 24 }

seniorSignalSD.c

  1 #include 
  2 #include 
  3 
  4 int main(int argc, char **argv)
  5 {
  6     int signum;
  7     int pid;
  8     signum = atoi(argv[1]);
  9     pid = atoi(argv[2]);
 10     union sigval value;
 11     value.sival_int = 100;
 12     sigqueue(pid,signum,value);
 13     printf("done!\n");
 14     return 0;
 15 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nXSNyjkn-1610010410864)(en-resource://database/829:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qw6KQrb2-1610010410865)(en-resource://database/831:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QeCSOPcz-1610010410866)(en-resource://database/833:1)]

信号量编程

// 创建一个新信号量或取得一个已有信号量;num_sems为信号量个数,flags为IPC_CREAT|00600创建信号量
int semget(key_t key, int num_sems, int sem_flags);

// 改变信号量的值;nsops为结构体sembuf的个数即信号量的个数,如果有2个信号量则sembuf是个结构体数组有2个结构体
int semop(int semid, struct sembuf *sops, unsigned nsops);

// 直接控制信号量信息,初始化信号量;sem_num为控制第几个信号量从第0个开始;command为SETVAL给信号量初始化,初始化时第四个参数为semun的结构体
int semctl(int sem_id, int sem_num, int command, ...);

让子进程先执行,父进程后执行
不加锁前父进程程序先执行,父进程先去拿锁(信号量值减1),但没有,(信号量值小于0为-1)则等待。运行子进程,结束后把锁放回去,父进程再去拿锁执行。

编程思路:
// 创建信号量/获取已有信号量
// 初始化信号量
// 子程序执行,把锁放回
// 父进程拿锁执行把锁放回
// 销毁信号量
 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 
  6 union semun {
  7     int val;    /* Value for SETVAL */
  8     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
  9     unsigned short  *array;  /* Array for GETALL, SETALL */
 10     struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
 11 };
 12 
 13 void pGetKey(int id)
 14 {
 15     struct sembuf sop;
 16     sop.sem_num = 0;
 17     sop.sem_op = -1;
 18     sop.sem_flg = SEM_UNDO;
 19     semop(id,&sop,1);
 20     printf("get key done!\n");
 21 }
 22 
 23 void vPutBackKey(int id)
 24 {
 25     struct sembuf sop;
 26     sop.sem_num = 0;
 27     sop.sem_op = 1;
 28     sop.sem_flg = SEM_UNDO;
 29     semop(id,&sop,1);
 30     printf("put back the key done!\n");
 31 }
 32 
 33 int main()
 34 {
 35     key_t key = ftok(".",1);
 36     int semid = semget(key,1,IPC_CREAT|00600);
 37     union semun initsem;
 38     initsem.val = 0;
 39     semctl(semid,0,SETVAL,initsem);
 40 
 41     int forkpid = fork();
 42     if(forkpid > 0)
 43     {
 44         pGetKey(semid);
 45         printf("this is father fork\n");
 46         vPutBackKey(semid);
 47         semctl(semid,0,IPC_RMID);
 48     }
 49     else if(forkpid == 0)
 50     {
 51         printf("this is child fork\n");
 52         vPutBackKey(semid);
 53     }
 54     else
 55     {
 56         printf("fork error!\n");
 57     }
 58     return 0;
 59 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g2vaN6od-1610010410867)(en-resource://database/835:1)]

消息队列、共享内存、信号量综合

消息队列用来发送和接收指令,共享内存用来发送和接收信息,信号量用来保证消息同步

服务端逻辑
// 创建共享内存
// 连接共享内存
// 创建消息队列
// 创建信号量
// 初始化信号量
// 不断读取消息队列中的指令
// 当读到r,p操作,接收共享内存中的信息,v操作
// 当读到q,退出循环
// 清空并销毁共享内存
// 销毁消息队列
// 销毁信号量

server.c

 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 void pGetKey(int semid)
  9 {
 10     struct sembuf initsem;
 11     initsem.sem_num = 0;
 12     initsem.sem_op = -1;
 13     initsem.sem_flg = SEM_UNDO;
 14     semop(semid,&initsem,1);
 15     printf("get key done!\n");
 16 }
 17 
 18 void vPutBackKey(int semid)
 19 {
 20     struct sembuf initsem;
 21     initsem.sem_num = 0;
 22     initsem.sem_op = 1;
 23     initsem.sem_flg = SEM_UNDO;
 24     semop(semid,&initsem,1);
 25     printf("put back key done!\n");
 26 }
 27 struct msgbuf {
 28     long mtype;       /* message type, must be > 0 */
 29     char mtext[1];
 30 };
 31 union semun {
 32     int val;    /* Value for SETVAL */
 33     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
 34     unsigned short  *array;  /* Array for GETALL, SETALL */
 35     struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
 36 };
 37 
 38 int main()
 39 {
 40     key_t key = ftok(".",1);
 41     struct msgbuf buf;
 42     int shmid = shmget(key,1024*4,IPC_CREAT|00600);
 43     char *shmaddr = shmat(shmid,NULL,0);
44 
 45     int msgid = msgget(key,IPC_CREAT|00600);
 46 
 47     int semid = semget(key,1,IPC_CREAT|00600);
 48     union semun initsem;
 49     initsem.val = 1;
 50     semctl(semid,0,SETVAL,initsem);
 51     while(1)
 52     {
 53         msgrcv(msgid,&buf,1,888,0);
 54         if(buf.mtext[0] == 'q')
 55             break;
 56         if(buf.mtext[0] == 'r')
 57         {
 58             pGetKey(semid);
 59             printf("read from shm:%s\n",shmaddr);
 60             vPutBackKey(semid);
 61         }
 62     }
 63     shmdt(shmaddr);
 64 
 65     shmctl(shmid,IPC_RMID,0);
 66     msgctl(msgid,IPC_RMID,0);
 67     semctl(semid,0,IPC_RMID);
 68     return 0;
 69 }
客户端逻辑
// 获取共享内存
// 连接共享内存
// 获取消息队列
// 获取信号量
// 不断让客户端请求用户输入指令
// 当用户输入r,p操作,向共享内存发送信息,v操作,清空标准输入缓冲区,发送r指令给消息队列
// 当用户输入q,发送q指令给消息队列,退出循环
// 清空共享内存

client.c

 1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 
  8 struct msgbuf
  9 {
 10     long mtype;
 11     char mtext[1];
 12 };
 13 
 14 void pGetKey(int semid)
 15 {
 16     struct sembuf initsem;
 17     initsem.sem_num = 0;
 18     initsem.sem_op = -1;
 19     initsem.sem_flg = SEM_UNDO;
 20     semop(semid,&initsem,1);
 21     printf("get key\n");
 22 }
 23 
 24 void vPutBackKey(int semid)
 25 {
 26     struct sembuf initsem;
 27     initsem.sem_num = 0;
 28     initsem.sem_op = 1;
 29     initsem.sem_flg = SEM_UNDO;
 30     semop(semid,&initsem,1);
 31     printf("put back key\n");
 32 }
 33 
 34 int main()
 35 {
 36     key_t key = ftok(".",1);
 37     int shmid = shmget(key,1024*4,0);
 38     char *shmaddr = shmat(shmid,NULL,0);
 39     int msgid = msgget(key,0);
 40     int semid = semget(key,0,0);
 41     printf("***************************************\n");
 42     printf("*                 IPC                 *\n");
 43     printf("*    Input r to send data to server.  *\n");
 44     printf("*    Input q to quit.                 *\n");
 45     printf("***************************************\n");
 46     char c;
 47     struct msgbuf msg;
 48     int flag = 1;
 49     while(flag)
 50     {
 51         printf("Please input command:");
 52         scanf("%c",&c);
 53         switch(c)
 54         {
 55         case 'r':
 56             printf("Data to send:");
 57             pGetKey(semid);
 58             scanf("%s",shmaddr);
 59             vPutBackKey(semid);
 60             while((c = getchar()) != '\n' && c != EOF);
 61             msg.mtype = 888;
 62             msg.mtext[0] = 'r';
 63             msgsnd(msgid,&msg,sizeof(msg.mtext),0);
 64             break;
 65         case 'q':
 66             msg.mtype = 888;
 67             msg.mtext[0] = 'q';
 68             msgsnd(msgid,&msg,sizeof(msg.mtext),0);
 69             flag = 0;
 70             break;
 71         default:
 72             printf("wrong input!\n");
 73             while((c = getchar()) != '\n' && c != EOF);
 74             break;
 75         }
 76     }
 77     shmdt(shmaddr);
 78     return 0;
 79 }

等待接收消息队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3Q1PTZy-1610010410868)(en-resource://database/839:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3KbqqXZ2-1610010410869)(en-resource://database/841:1)]
输入r,客户端执行p操作,S=0,继续执行向共享内存发送信息,客户端执行v操作,S=1,向消息队列发送指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWmiMex7-1610010410870)(en-resource://database/843:1)]
服务端收到r指令,执行p操作,S=0,继续执行接收共享内存信息,执行v操作,S=1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZsiMRSr7-1610010410871)(en-resource://database/845:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ms4e1m9l-1610010410872)(en-resource://database/847:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vHROPhl9-1610010410873)(en-resource://database/849:1)]

同步、互斥、信号量初值的设定、并发和并行

同步: 进程之间相互依赖。前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。(同步一般信号量初值设为0,例子见信号量编程)

互斥: 两个或两个以上的进程,不能同时进入关于同一组共享变量的临界区域,一个进程正在访问临界资源,另一个要访问该资源的进程必须等待。(互斥的信号量初值一般设为1,例子见消息队列、共享内存、信号量综合)

关于信号量的初值的设定
信号量的初值为最多允许几个进程同时进入互斥段,信号量的值为负值,意思是有几个进程在等待

互斥段: 被加锁和解锁包围的代码段

临界资源: 一次仅允许一个进程使用的资源

并发: 几个进程都处于运行状态,两种并发方式是同步和互斥

并行: 在多处理器上进程可以重叠执行(在同一时间执行多个进程)

五种通信方式总结

  1. 管道:速度慢,容量有限,只有父子进程能通讯
  2. FIFO:任何进程间都能通讯,但速度慢
  3. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
  4. 信号量:不能传递复杂消息,只能用来同步
  5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

线程

参考文档

线程概述

进程与线程的区别

典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。

进程是线程的容器。一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。线程没有单独的地址空间,一个线程死掉就等于整个进程死掉。
所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

使用线程的理由

  • 【理由一】:
    启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
    启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据。

  • 【理由二】:
    线程间通信更方便。
    对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

异步

线程的同步执行是顺序执行;异步执行是同时执行

线程同步和互斥

线程同步: 按预定的先后次序运行
线程互斥: 对于共享的进程系统资源,只允许一个线程访问,让其它线程等待。

线程编程

创建线程pthread_create和pthread_self

创建线程threadCreate.c

// 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
第1个参数:要创建的线程名,指针类型
第2个参数:属性,定为NULL
第3个参数:创建线程后要执行的函数,函数指针类型
第4个参数:第3个参数函数中的参数

// 获取当前线程号
pthread_self()
  1 #include 
  2 #include 
  3 
  4 void *func1(void *arg)
  5 {
  6     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
  7     printf("t1:param is %d\n",*((int *)arg));
  8 }
  9 
 10 int main()
 11 {
 12     int ret;
 13     pthread_t t1;
 14     int param = 100;
 15     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 16     if(ret == 0)
 17     {
 18         printf("create thread t1 successfully!\n");
 19     }
 20     printf("main thread:%ld\n",(unsigned long)pthread_self());
 21     while(1);// 为了让主线程阻塞,待t1线程运行
 22     return 0;
 23 }

while(1);// 为了让主线程阻塞不让它结束运行,待t1线程运行。如果主线程退出会释放进程空间,让其它线程没有空间运行程序
编译指令:gcc threadCreate.c -lpthread -o threadCreate
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhAWrXFv-1610010410874)(en-resource://database/851:1)]

阻塞主线程

阻塞主线程threadBlock.c

// 线程等待
int pthread_join(pthread_t thread, void **retval);
第1个参数:要等待这个线程执行完毕
第2个参数:退出等待时的状态码,NULL表示不关心状态码调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
子线程不调用exit,join也阻塞调用它的线程直到子线程结束

// 线程退出
void pthread_exit(void *retval);
参数:线程退出时的状态码
  1 #include 
  2 #include 
  3 
  4 void *func1(void *arg)
  5 {
  6     static int ret = 10;
  7     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
  8     printf("t1:param is %d\n",*((int *)arg));
  9     pthread_exit((void *)&ret);
 10 }
 11 
 12 int main()
 13 {
 14     int ret;
 15     pthread_t t1;
 16     int param = 100;
 17     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 18     if(ret == 0)
 19     {
 20         printf("create thread t1 successfully!\n");
 21     }
 22     printf("main thread:%ld\n",(unsigned long)pthread_self());
 23     int *pret = NULL;
 24     pthread_join(t1,(void *)&pret);
 25     printf("main thread:t1 quit %d\n",*pret);
 26     return 0;
 27 }

static int ret = 10;指定为static是为了func1运行结束后仍然保持ret的值传给pthread_join;
int *pret = NULL; pthread_join(t1,(void *)&pret);由于参数要求是二级指针,所以一级指针的值可以改变,初值设为NULL也没事,会被pthread_exit中的状态码重新赋值;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTx4onkj-1610010410875)(en-resource://database/853:1)]

退出状态码为字符串

  1 #include 
  2 #include 
  3 
  4 void *func1(void *arg)
  5 {
  6     static int ret = 10;
  7     static char *p = "t1 is run out!";
  8     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
  9     printf("t1:param is %d\n",*((int *)arg));
 10     pthread_exit((void *)p);
 11 }
 12 
 13 int main()
 14 {
 15     int ret;
 16     pthread_t t1;
 17     int param = 100;
 18     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 19     if(ret == 0)
 20     {
 21         printf("create thread t1 successfully!\n");
 22     }
 23     printf("main thread:%ld\n",(unsigned long)pthread_self());
 24     char *pret = NULL;
 25     pthread_join(t1,(void *)&pret);
 26     printf("main thread:%s\n",pret);
 27     return 0;
 28 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IuxlyrGd-1610010410876)(en-resource://database/855:1)]

线程共享内存空间的代码验证

创建全局变量g_data,在各线程中++,如果打印结果都不重复说明各线程共享这个数据

  1 #include 
  2 #include 
  3 
  4 int g_data = 50;
  5 
  6 void *func1(void *arg)
  7 {
  8     static char *p = "t1 is run out!";
  9     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
 10     printf("t1:param is %d\n",*((int *)arg));
 11     while(1)
 12     {
 13         printf("t1:g_data = %d\n",g_data++);
 14         sleep(1);
 15     }
 16     pthread_exit((void *)p);
 17 }
 18 
 19 void *func2(void *arg)
 20 {
 21     static char *p = "t2 is run out!";
 22     printf("t2:%ld is created!\n",(unsigned long)pthread_self());
 23     printf("t2:param is %d\n",*((int *)arg));
 24     while(1)
 25     {
 26         printf("t2:g_data = %d\n",g_data++);
 27         sleep(1);
 28     }
 29     pthread_exit((void *)p);
 30 }
 31 
 32 void *func3(void *arg)
 33 {
 34     static char *p = "t3 is run out!";
 35     printf("t3:%ld is created!\n",(unsigned long)pthread_self());
 36     printf("t3:param is %d\n",*((int *)arg));
 37     while(1)
 38     {
 39         printf("t3:g_data = %d\n",g_data++);
 40         sleep(1);
 41     }
 42     pthread_exit((void *)p);
 43 }
 44 
 45 int main()
 46 {
 47     int ret;
 48     pthread_t t1;
 49     pthread_t t2;
 50     pthread_t t3;
 51     int param = 100;
 52     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 53     if(ret == 0)
 54     {
 55         printf("main:create thread t1 successfully!\n");
 56     }
 57 
 58     ret = pthread_create(&t2,NULL,func2,(void *)¶m);
 59     if(ret == 0)
 60     {
 61         printf("main:create thread t2 successfully!\n");
 62     }
 63 
 64     ret = pthread_create(&t3,NULL,func3,(void *)¶m);
 65     if(ret == 0)
 66     {
 67         printf("main:create thread t3 successfully!\n");
 68     }
 69 
 70     printf("main thread:%ld\n",(unsigned long)pthread_self());
 71     while(1)
 72     {
 73         printf("main thread:g_data = %d\n",g_data++);
 74         sleep(1);
 75     }
 76     char *pret1 = NULL;
 77     char *pret2 = NULL;
 78     char *pret3 = NULL;
 79     pthread_join(t1,(void *)&pret1);
 80     pthread_join(t2,(void *)&pret2);
 81     pthread_join(t3,(void *)&pret3);
 82     printf("main thread:%s\n",pret1);
 83     printf("main thread:%s\n",pret2);
 84     printf("main thread:%s\n",pret3);
 85     return 0;
 86 }

从打印结果看,没有重复的数据,说明各线程是共享全局变量的数据的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SY3jKKr1-1610010410878)(en-resource://database/857:1)]

线程同步之互斥锁加锁解锁

// 创建锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
第1个参数:指针类型
第2个参数:锁的属性,默认设置为NULL

// 销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

mutex是一把锁,被加锁和解锁包围的代码是共享资源,在某个线程执行时程序会保证加过锁的代码连续运行,哪怕里面有sleep,解锁后各线程再竞争,再执行各线程中加锁的代码
  1 #include 
  2 #include 
  3 
  4 pthread_mutex_t mutex;
  5 
  6 void *func1(void *arg)
  7 {
  8     pthread_mutex_lock(&mutex);
  9     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
 10     printf("t1:param is %d\n",*((int *)arg));
 11     pthread_mutex_unlock(&mutex);
 12 }
 13 
 14 void *func2(void *arg)
 15 {
 16     pthread_mutex_lock(&mutex);
 17     printf("t2:%ld is created!\n",(unsigned long)pthread_self());
 18     printf("t2:param is %d\n",*((int *)arg));
 19     pthread_mutex_unlock(&mutex);
 20 }
 21 
 22 void *func3(void *arg)
 23 {
 24     pthread_mutex_lock(&mutex);
 25     printf("t3:%ld is created!\n",(unsigned long)pthread_self());
 26     printf("t3:param is %d\n",*((int *)arg));
 27     pthread_mutex_unlock(&mutex);
 28 }
 29 
 30 int main()
 31 {
 32     int ret;
 33     pthread_t t1;
 34     pthread_t t2;
 35     pthread_t t3;
 36     int param = 100;
 37 
 38     pthread_mutex_init(&mutex,NULL);
 39 
 40     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 41     if(ret == 0)
 42     {
 43         printf("main:create thread t1 successfully!\n");
 44     }
 45 
 46     ret = pthread_create(&t2,NULL,func2,(void *)¶m);
 47     if(ret == 0)
 48     {
 49         printf("main:create thread t2 successfully!\n");
 50     }
 51 
 52     ret = pthread_create(&t3,NULL,func3,(void *)¶m);
 53     if(ret == 0)
 54     {
 55         printf("main:create thread t3 successfully!\n");
 56     }
 57 
 58     printf("main thread:%ld\n",(unsigned long)pthread_self());
 59     pthread_join(t1,NULL);
 60     pthread_join(t2,NULL);
 61     pthread_join(t3,NULL);
 62 
 63     pthread_mutex_destroy(&mutex);
 64     return 0;
 65 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QP03Jx9g-1610010410879)(en-resource://database/859:1)]

对共享内存的加锁和解锁

对g_data这个共享数据加锁解锁,让其它线程睡1秒,当其它线程同时在sleep时只能执行t1线程,再用锁保证其代码的执行
一定要在t2线程也加锁解锁,因为t1中g_data没有等于3时,由于t2也加了锁,t2会被阻塞直到t1一直加到3解锁以后再能运行t2

并不是被加锁和解锁包围的代码先执行

对互斥量进行加锁后,任何其它试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。

如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行
  1 #include 
  2 #include 
  3 
  4 pthread_mutex_t mutex;
  5 
  6 int g_data = 0;
  7 
  8 void *func1(void *arg)
  9 {
 10     pthread_mutex_lock(&mutex);
 11     while(1)
 12     {
 13         printf("t1:g_data = %d\n",g_data++);
 14         sleep(1);
 15         if(g_data == 3)
 16         {
 17             pthread_mutex_unlock(&mutex);
 18             printf("t1:quit==================================================================\n");
 19             pthread_exit(NULL);
 20         }
 21     }
 22 }
 23 
 24 void *func2(void *arg)
 25 {
 26     while(1)
 27     {
 28         printf("t2:g_data = %d\n",g_data);
 29         pthread_mutex_lock(&mutex);
 30         g_data++;
 31         pthread_mutex_unlock(&mutex);
 32         sleep(1);
 33     }
 34 }
 35 
 36 int main()
 37 {
 38     int ret;
 39     pthread_t t1;
 40     pthread_t t2;
 41     int param = 100;
 42 
 43     pthread_mutex_init(&mutex,NULL);
 44     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 45     ret = pthread_create(&t2,NULL,func2,(void *)¶m);
 46 
 47     while(1)
 48     {
 49         printf("main:g_data = %d\n",g_data);
 50         sleep(1);
 51     }
 52     pthread_join(t1,NULL);
 53     pthread_join(t2,NULL);
 54 
 55     pthread_mutex_destroy(&mutex);
 56     return 0;
 57 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xysNcGmA-1610010410880)(en-resource://database/861:1)]

死锁

死锁要有2把锁,在两个线程中分别相邻加锁,顺序颠倒,这样让2把锁分别在2个不同的线程中,导致第二次想拿锁的时候都拿不到,两个线程都会被阻塞等待,造成死锁
  1 #include 
  2 #include 
  3 
  4 pthread_mutex_t mutex;
  5 pthread_mutex_t mutex2;
  6 
  7 int g_data = 0;
  8 
  9 void *func1(void *arg)
 10 {
 11     int i;
 12     pthread_mutex_lock(&mutex);
 13     sleep(1);
 14     pthread_mutex_lock(&mutex2);
 15     for(i = 0; i < 5; i++)
 16     {
 17         printf("t1:g_data = %d\n",g_data++);
 18     }
 19     pthread_mutex_unlock(&mutex);
 20 }
 21 
 22 void *func2(void *arg)
 23 {
 24     while(1)
 25     {
 26         pthread_mutex_lock(&mutex2);
 27         sleep(1);
 28         pthread_mutex_lock(&mutex);
 29         printf("t2:g_data = %d\n",g_data);
 30         g_data++;
 31         pthread_mutex_unlock(&mutex);
 32         sleep(1);
 33     }
 34 }
 35 
 36 int main()
 37 {
 38     int ret;
 39     pthread_t t1;
 40     pthread_t t2;
 41     int param = 100;
 42 
 43     pthread_mutex_init(&mutex,NULL);
 44     pthread_mutex_init(&mutex2,NULL);
 45     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 46     ret = pthread_create(&t2,NULL,func2,(void *)¶m);
 47 
 48     printf("main:g_data = %d\n",g_data);
 49 
 50     pthread_join(t1,NULL);
 51     pthread_join(t2,NULL);
 52 
 53     pthread_mutex_destroy(&mutex);
 54     pthread_mutex_destroy(&mutex2);
 55     return 0;
 56 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SW4cw4PI-1610010410881)(en-resource://database/865:1)]
下图是程序执行的顺序,3和4都被阻塞等待造成死锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBeTAqA3-1610010410882)(en-resource://database/863:1)]

线程的条件控制实现线程的同步

让其它线程对g_data++,当g_data==3时在t1线程打印quit

// 初始化
第2个参数:属性,一般为NULL
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

// 等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

// 触发
int pthread_cond_signal(pthread_cond_t *cond);

// 销毁
int pthread_cond_destroy(pthread_cond_t *cond);
  1 #include 
  2 #include 
  3 
  4 pthread_mutex_t mutex;
  5 pthread_cond_t cond;
  6 
  7 int g_data = 0;
  8 
  9 void *func1(void *arg)
 10 {
 11     while(1)
 12     {
 13         pthread_cond_wait(&cond,&mutex);
 14         printf("t1:quit==================================================================\n");
 15         printf("t1:g_data = %d\n",g_data);
 16         g_data = 0;
 17         sleep(1);
 18     }
 19 }
 20 
 21 void *func2(void *arg)
 22 {
 23     while(1)
 24     {
 25         printf("t2:g_data = %d\n",g_data);
 26         pthread_mutex_lock(&mutex);
 27         g_data++;
 28         if(g_data == 3)
 29         {
 30             pthread_cond_signal(&cond);
 31         }
 32         pthread_mutex_unlock(&mutex);
 33         sleep(1);
 34     }
 35 }
 36 
 37 int main()
 38 {
 39     int ret;
 40     pthread_t t1;
 41     pthread_t t2;
 42     int param = 100;
 43 
 44     pthread_cond_init(&cond,NULL);
 45 
 46     pthread_mutex_init(&mutex,NULL);
 47     ret = pthread_create(&t1,NULL,func1,(void *)¶m);
 48     ret = pthread_create(&t2,NULL,func2,(void *)¶m);
 49 
 50     pthread_join(t1,NULL);
 51     pthread_join(t2,NULL);
 52 
 53     pthread_mutex_destroy(&mutex);
 54     pthread_cond_destroy(&cond);
 55     return 0;
 56 }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3Iw3BKT-1610010410883)(en-resource://database/869:1)]

用宏作静态初始化,使用init函数的都是动态初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0QAxUCxG-1610010410884)(en-resource://database/871:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d5VCg3WM-1610010410885)(en-resource://database/873:1)]

线程同步——生产者消费者问题

生产者消费者问题

有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。

用互斥锁编程实现
  1 #include 
  2 #include 
  3 #define LOOP_COUNT 5
  4 
  5 pthread_mutex_t mutex;
  6 pthread_t thread_producer,thread_consumer;
  7 
  8 void *producer(void *arg)
  9 {
 10     //let main thread run first,and then thread_produncer run
 11     sleep(1);
 12     int count = 0;
 13     while(count++ < LOOP_COUNT)
 14     {
 15         pthread_mutex_lock(&mutex);
 16         printf("producer put a product to buffer.\n");
 17         pthread_mutex_unlock(&mutex);
 18         //insure thread_consumer to run
 19         sleep(3);
 20     }
 21 //  pthread_exit(NULL);
 22 }
 23 
 24 void *consumer(void *arg)
 25 {
 26     //let main thread run first,and run after thread_producer
 27     sleep(2);
 28     int count = 0;
 29     while(count++ < LOOP_COUNT)
 30     {
 31         pthread_mutex_lock(&mutex);
 32         printf("consumer get a product from buffer.\n");
 33         pthread_mutex_unlock(&mutex);
 34         //insure thread_producer to run
 35         sleep(3);
 36     }
 37 //  pthread_exit(NULL);
 38 }
 39 
 40 int main()
 41 {
 42     pthread_mutex_init(&mutex,NULL);
 43     int ret_producer = pthread_create(&thread_producer,NULL,producer,NULL);
 44     if(ret_producer == 0)
 45     {
 46         printf("main thread:create thread producer successfully!\n");
 47     }
 48     int ret_consumer = pthread_create(&thread_consumer,NULL,consumer,NULL);
 49     if(ret_consumer == 0)
 50     {
 51         printf("main thread:create thread consumer successfully!\n");
 52     }
 53 
 54     pthread_join(thread_producer,NULL);
 55     pthread_join(thread_consumer,NULL);
 56 
 57     pthread_mutex_destroy(&mutex);
 58     return 0;
 59 }

让子线程睡2秒以上才能保证不抢线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rN4HVHOy-1610010410886)(en-resource://database/875:1)]

网络编程

网络编程概述

单机进程间通信的方式都是依赖Linux内核,无法实现多机通讯

网络编程一定涉及地址去找到设备,地址包括IP地址(找到设备)和端口号(各种服务跑在不同的端口上,端口号为了区分这些不同的服务)

数据的交流:协议(数据格式)eg. 单片机和PC通信用uart串口协议,Linux网络编程用tcp\udp协议(Socket套接字网络编程)

tcp协议:面向连接,要先连接A和B端,然后类似打电话进行交流,可靠,用于精确的数据传输场景

udp协议:面向报文。不用连接,A给B发信息,B不一定能收到,不可靠,用于大量的数据传输场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3emr3nf-1610010410887)(en-resource://database/877:1)]

字节序

字节序就是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序。

8(bit)位1个字节(Byte),16位1个字(Word),双字是32位,1个字节1个存储空间,双字是4个存储空间

常见的字节序是:小端字节序(LE)和大端字节序(BE)
小端字节序:将低序字节存储在起始地址,即将04存在4000,03存在4001地址…
大端字节序:将高序字节存储在起始地址,反过来
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6ZghFfb-1610010410888)(en-resource://database/879:1)]

x86系列CPU都是little-endian的字节序,网络字节顺序采用big endian排序方式

socket编程步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHtGalCk-1610010410889)(en-resource://database/881:1)]

socket网络编程实战

寻找关键字在哪个库里

cd /usr/include/切换到include目录
grep "struct sockaddr_in {" * -nir

  • 在当前目录查找
    r 递归查找
    n 找出来显示行号
    i 不区分大小写来找

网络编程实战

用户端口号一般使用5000~9000,低于3000的端口号是供操作系统使用的
5000~9000存储的是主机字节序,要转换成网络字节序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZsRtvApF-1610010410890)(en-resource://database/883:1)]

// 创建套接字
int socket(int domain, int type, int protocol);
第1个参数:使用什么协议,AF_INET是IPv4网络协议
第2个参数:协议的类型,SOCK_STREAM是tcp协议,提供按序列的可靠的双向基于连接的字节流
SOCK_DGRAM是udp协议,采用报文的方式
第3个参数:如果只使用一个协议,这个参数就是0,否则是其它协议的代码
返回值:失败返回-1,成功返回socket的文件描述符

// 为套接字添加信息
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第1个参数:socket的文件描述符
第2个参数:改用struct sockaddr_in s_addr;但要强转成struct sockaddr *,切换到usr/include目录,查找该字段grep "struct sockaddr_in {" * -nir,在vi linux/in.h +184中184行
sin_family:地址家族为AF_INET
sin_port:端口号范围5000~9000,要将主机端口号转换成网络字节序,调用htons()函数
sin_addr:是个结构体,通过grep "struct in_addr {" * -nir搜索,在vi linux/in.h +56
sin_addr.s_addr:结构体中的一个成员,是网络地址,要将主机地址转换成网络能识别的地址,用inet_aton("127.0.0.1");
第3个参数:第2个参数结构体的大小

//监听网络连接
int listen(int sockfd, int backlog);
第1个参数:socket的文件描述符
第2个参数:最大监听数量
如果没有监听到会卡在这

//监听到有客户端接入,接受一个完成3次握手的连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第1个参数:socket的文件描述符
第2、3个参数:NULL
返回值:成功返回一个客户端的socket文件描述符,失败返回-1

socket服务端

  1 #include 
  2 #include 
  3 #include 
  4 //#include 
  5 #include 
  6 #include 
  7 #include 
  8 
  9 int main()
 10 {
 11     //1.socket
 12     int socket_fd = socket(AF_INET,SOCK_STREAM,0);
 13     if(socket_fd == -1)
 14     {
 15         perror("socket");
 16         exit(-1);
 17     }
 18     //2.bind
 19     struct sockaddr_in s_addr;
 20     s_addr.sin_family = AF_INET;
 21     s_addr.sin_port = htons(8989);
 22     inet_aton("192.168.0.100",&s_addr.sin_addr);
 23     bind(socket_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
 24     //3.listen
 25     listen(socket_fd,10);
 26     //4.accept
 27     int c_fd = accept(socket_fd,NULL,NULL);
 28     //5.read
 29     //6.write
 30     printf("connect!\n");
 31     while(1);
 32     return 0;
 33 }

用Windows测试连接

首先查看Linux虚拟机的ip地址
ifconfig命令查看到是192.168.0.100

在Windows系统中运行cmd,ping 192.168.0.100能通说明电脑作为客户端和Linux虚拟机服务端能连接

在Linux系统中运行服务端程序
在Windows系统中用telnet连接
telnet 192.168.0.100 8989

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6jLtVVxL-1610010410892)(en-resource://database/889:1)]
连接成功

连接成功时获取客户端地址

  1 #include 
  2 #include 
  3 #include 
  4 //#include 
  5 #include 
  6 #include 
  7 #include 
  8 
  9 int main()
 10 {
 11     int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
 12     if(socket_fd == -1)
 13     {
 14         perror("socket");
 15         exit(-1);
 16     }
 17 
 18     struct sockaddr_in s_addr;
 19     struct sockaddr_in c_addr;
 20     memset(&s_addr, 0, sizeof(struct sockaddr_in));
 21     memset(&c_addr, 0, sizeof(struct sockaddr_in));
 22 
 23     s_addr.sin_family = AF_INET;
 24     s_addr.sin_port = htons(8989);
 25     inet_aton("192.168.0.100", &s_addr.sin_addr);
 26     bind(socket_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
 27     listen(socket_fd, 10);
 28     int clen = sizeof(struct sockaddr_in);
 29     int client_fd = accept(socket_fd, (struct sockaddr *)&c_addr, &clen);
 30     if(client_fd == -1)
 31     {
 32         perror("client");
 33         exit(-1);
 34     }
 35     printf("get connected:%s\n", inet_ntoa(c_addr.sin_addr));
 36     while(1);
 37     return 0;
 38 }

在Windows系统中的cmd命令行中输入telnet 192.168.0.100 8989
连接成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qv7MZahk-1610010410893)(en-resource://database/890:1)]

你可能感兴趣的:(Linux,linux,c语言)