提卡网站建设北京王府井集团股份有限公司

张小明 2026/3/12 16:06:49
提卡网站建设,北京王府井集团股份有限公司,wordpress次元主题,wordpress在线咨询大纲IO#xff08;input、output#xff09;标准IO、文件IO、库、Linux IO模型进程#xff1a;process进程基础、进程间通信#xff1a;无名管道(pipe)、有名管道(fifo)、信号(signal)、共享内存(shared memory)、消息队列(message queue)、信号灯集(semphore set)线程(thr…大纲IOinput、output标准IO、文件IO、库、Linux IO模型进程process进程基础、进程间通信无名管道(pipe)、有名管道(fifo)、信号(signal)、共享内存(shared memory)、消息队列(message queue)、信号灯集(semphore set)线程(thread)、同步、互斥、条件变量1. 标准IO标准IO是C语言标准库中提供的一组用于输入输出操作的函数定义在stdio.h头文件中。1通过缓冲机制提高效率2并围绕流FILE *进行操作3标准IO默认打开三个流stdin标准输入、stdout标准输出和stderr标准错误。特点1) 通过缓冲机制减少系统调用提高效率缓冲机制在数据传输过程中通过在内存中设置一定大小的缓冲区将数据临时存储在缓冲区中以减少系统调用的次数提高数据处理的效率。系统调用应用层与内核层之间的一组接口。2) 围绕着流进行操作流(stream)的概念它将数据的输入输出看作是数据的流入和流出。操作系统用结构体FILE标识一个流结构体包含了标准 I/O 库函数为管理文件所需要的所有信息。FILE数据类型的定义也在stdio.h中是操作系统帮我们创建的我们用指向这个结构体的指针FILE * 来找到这个结构体的位置进行操作当采用fopen 打开一个流时会返回一个指向流对象的指针指针类型为FILE *3) 标准IO默认打开三个流stdin(标准输入)、stdout(标准输出)、stderr(标准错误)他们默认在终端上输入和输出1.1.缓冲机制三种1) 全缓存和文件相关刷新缓存的条件1.程序正常结束 returnmain、exit2.强制刷新fflushNULL3.缓冲区满2) 行缓存和终端相关刷新缓存的条件1.程序正常结束 returnmain、exit2.强制刷新fflushNULL3.缓冲区满4.\n3) 不缓存标准错误的话不缓存缓冲区大小一般为1kb也就是1024个字节。综上当我们每次要打印数据时并不是将数据直接发送给标准输出设备也就是并直接发送给显示器而是将要打印的数据先存放到缓存区当缓冲存数据满时或者遇到\n或者程序结束时或者手动刷新缓存区时缓冲区才会把数据传输到标准输出设备中也就是显示器中进行输出。1.2. 标准IO的函数接口通过流对文件或终端中的数据进行读写操作打开文件fopen关闭文件fclose读写操作fgets、fputs、fread、fwrite定位操作rewind、fseek、ftell1.2.1. 打开文件#include stdio.h FILE *fopen(const char *pathname, const char *mode); 功能打开文件 参数 pathname: 打开的文件路径 mode: 打开的方式 r只读流被定位到文件开头 r可读可写流被定位到文件开头 w只写文件不存在创建文件存在清空流被定位到文件开头 w可读可写文件不存在创建文件存在清空流被定位到文件开头 a只写文件不存在创建存在追加流被定位到文件末尾从文章末尾开始写 a可读可写文件不存在创建存在追加开始进行读时从头读进行写时流被定位到文件末尾 返回值成功返回一个 FILE 指针文件流 失败NULL并且设置全局变量 errno (错误码)来标识错误。r :只读方式打开文件 , 文件必须存在 ; 文件不存在打开失败 ;流在开头w :打开只写文件 , 文件不存在创建文件 , 文件存在清空文件 ;流在开头a :打开只写文件 , 文件不存在创建文件 , 文件存在追加文件 ;流在结尾 :读写方式打开文件 ;a的读操作流在开头1.2.2. 关闭文件int fclose(FILE* stream); 功能关闭文件 参数stream文件流 返回值如果流成功关闭则该方法返回零。如果失败则返回 EOF。1.2.3. 读写文件读字符串 #include stdio.h char *fgets(char *s, int size, FILE *stream); 功能从文件中读取一个字符串 参数s存放所读字符串的数组的首地址 size读取的大小 stream文件流 返回值成功读取的字符串所在数组的首地址 失败读到文件末尾NULL 特性1. 一次调用最多读取一行数据,遇到\n或者达到文件末尾后不在继续下一行 2. 实际读到个数为size-1个末尾自动添加\0 写字符串 #include stdio.h int fputs(const char *s, FILE *stream); 功能向文件或终端中写一个字符串 参数s要写入的内容 stream文件流 返回值成功非负整数 失败EOF读二进制 #include stdio.h size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能从文件流读取多个元素(将二进制数据从文件读出) 参数ptr用来存放读取元素(可以用来存放任意类型的数据) size元素大小 sizeof(数据类型) nmemb: 读取的对象个数 stream要读取的文件 返回值成功读取元素的个数 失败或读取到文件尾0 写二进制 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 功能将二进制数据写入文件 参数ptr 是一个指针保存要输出数据的空间的地址。 size 要写入的字节数 sizeof(数据类型) nmemb : 要进行写入元素的个数 stream: 目标文件流指针 返回值成功写元素的个数 失败-11.2.4. 定位操作rewindvoid rewind(FILE* stream); 功能将文件的位置指针定位到起始位置fseek#include stdio.h int fseek(FILE *stream, long offset, int whence); 功能文件的定位操作 参数stream文件流 offset偏移量正数表示向后文件尾部偏移负数表示向文件开头偏移 whence相对位置 SEEK_SET 相对于文件开头 SEEK_CUR 相对于文件当前位置 SEEK_END 相对于文件末尾 返回值成功0 失败-1 注当打开方式为a或a时fseek不起作用 补充其中SEEK_SET、SEEK_CUR、SEEK_END依次为0、1、2ftelllong ftell(FILE *stream) 功能获取当前的文件位置 参数要检测的文件流 返回值成功当前位置指针位置 失败-1练习实现head -h 文件名命令的功能./a.out -5 xx.catoi: 123 -- 123argv[1]: -5argv[1]1: 5 //1目的是去掉-思想循环打印数换行来一行行数1,打印这行知道换行的数量达到n就结束。#include stdio.h #include string.h #include stdlib.h int main(int argc, char const *argv[]) { if (argc ! 3) { printf(err\n); return -1; } FILE *fp fopen(argv[2], r); if (fp NULL) { perror(fp err); return -1; } char buf[32]{}; int num atoi(argv[1] 1); for (int i 0; i num; i) { if (fgets(buf, 32, fp) ! NULL) { if (buf[buflen(buf) - 1] \n) fputs(buf, stdout); } } fclose(fp); return 0; }2. 文件IO又称系统IO是系统调用是操作系统提供的函数接口在posix(可移植操作系统接口)中定义的一组输入输出的函数特点1.没有缓冲机制每次操作都会经过系统调用相比较标准IO效率更低2.围绕文件描述符进行操作文件描述符是非负整数3.默认打开三个文件描述符分别为0标准输入、1标准输出、2标准错误4.除目录外其他任意类型的文件都可以操作 b、c、-、l、s、p问题打开三个文件描述符3、4、5关闭3以后重新打开一个文件描述符是几答还是3问题一个进程的文件描述符最大到几最多能打开多少个文件描述符最多能打开多少个文件?答一个进程的文件描述符最大到1023(0-1023),最多能打开1024个文件描述符, 最多能打开1024-31021个文件。2.1. 文件IO的函数接口2.1.1. 打开文件#include sys/types.h #include sys/stat.h #include fcntl.h int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); 功能打开文件 参数pathname: 文件路径名 flags打开方式 O_RDONLY只读 O_WRONLY只写 O_RDWR可读可写 O_CREAT不存在创建 O_TRUNC清空 O_APPEND追加 返回值成功文件描述符 失败-1 补充 1.多种打开方式一起使用时采用位或 | 来分隔两种打开方式因为打开方式是二进制数 2.当打开方式使用 O_CREAT不存在创建的时候需要第三个参数创建文件的权限 文件权限权限值 ~umask umask: 0002 //文件权限掩码 0666 (~umask) 110 110 110 111 111 101 || 110 110 100 0664 最终变化为其他用户没有写权限2.1.2. 关闭文件int close(int fd); 参数fd文件描述符实例#include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h int main() { int fd open(./test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);//写|创建|清空权限664 if (fd 0) { perror(fd err); return -1; } printf(%d, fd);//打印一下看看文件描述符 close(fd); //关闭文件 return 0; }2.1.3. 读写文件#include unistd.h ssize_t read(int fd, void *buf, size_t count); 功能从一个已经打开的可读文件中读取数据 参数fd 文件描述符 buf 读到数据的存放位置 count 期望的个数//指定多少字符就读取多少字符 返回值 成功实际读到的个数(小于期望的值说明实际没这么多)//所见即所得 读到文件结尾返回0 失败返回值-1并设置errno号 #include unistd.h ssize_t write(int fd, const void *buf, size_t count); 功能向指定的文件描述符中写入count个字节的数据 参数fd 文件描述符 buf 要写的内容 count 期望写入的字节数 返回值成功实际写入数据的个数 失败-1三种读的对比fgets: NULL末尾或失败fread: 0末尾或失败read : 0末尾 -1失败2.1.4. 定位操作#include sys/types.h #include unistd.h off_t lseek(int fd, off_t offset, int whence); 功能设置文件的偏移位置 参数fd 文件描述符 offset 偏移量 正数向文件结尾位置移动 负数向文件开始位置移动 whence 相对位置 或用01和2代替 SEEK_SET 开始位置 SEEK_CUR 当前位置 SEEK_END 结尾位置 返回值成功文件的当前位置 失败-1练习文件IO实现cp功能。cp 源文件 新文件名#include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.h int main(int argc, char const *argv[]) { char buf[32] {}; int n; int fd open(argv[1], O_RDONLY); if (fd 0) { perror(fd err); return -1; } int fd2 open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0666); if (fd2 0) { perror(fd2 err); return -1; } if (argc ! 3) { printf(输入格式 err\n); return -1; } while ((n read(fd, buf, 32)) 0) { write(fd2, buf, n); } close(fd); close(fd2); return 0; }2.2. 标准IO和文件IO的区别标准IO文件IO概念在 C 库中定义的一组输入输出的函数在 posix 中定义的一组输入输出的函数特点1. 有缓冲区减少系统调用提高效率2. 围绕流操作FILE *3. 默认打开三个流stdin\stdout\stderr4. 只操作普通文件-5.可移植性相对较好1. 无缓冲区每次操作都引起系统调用2. 围绕文件描述符操作3. 默认打开三个文件描述符0\1\24. 除目录外其他文件bc-lsp5.可移植性相对较差函数打开文件fopen\freopen关闭文件fclose读写文件fgetc/fputc 读取写入字符fgets/fputs fread/fwrite文件定位fseek/rewind/ftell打开文件open关闭文件close读写文件read/write文件定位lseek3. 库3.1. 库的定义库文件是计算机上的一类文件提供给使用者一些开箱即用的变量、函数或类。库是一种可执行代码的二进制形式。后缀Linux.so动态库.a静态库 Windows.dell不同系统的库互不兼容lib库文件 实现函数的定义include:头文件包含函数的声明3.2. 库的分类静态库在程序编译时会被复制到目标代码中以.a结尾优点程序运行时将不再需要该静态库运行时无需加载库运行速度更快。缺点静态库中的代码复制到了程序中因此体积较大静态库升级后程序需要重新编译链接。动态库在程序运行时才会被载入到代码中。也叫共享库以.so结尾优点程序在执行时加载动态库代码体积小程序升级更简单不同应用程序如果调用相同的库那么在内存里只需要有一份该共享库的实例。缺点运行时需要动态库的存在移植性较差。静态库、动态库本质的区别是代码载入的时刻不同3.3. 库的制作静态库的制作1-将源文件编译生成目标文件gcc -c xxx.c -o xxx.o2-创建静态库用ar命令它将很多.o转换成.aar crs libxxx.a xxx.o静态库文件名的命名规范是以lib为前缀紧接着跟静态库名扩展名为.a3-测试使用静态库gcc xxx.c -L. -l指定库名 // -L指定库的路径 -l指定库名执行 ./a.out动态库的制作1-用gcc来创建共享库gcc -fPIC -c xxx.c -o xxx.o-fPIC 创建与地址无关的编译程序 (不和路径进行关联可以移动)gcc -shared -o libxxx.so xxx.o2-把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)3-测试动态库使用 gcc main.c -l指定库名当加载动态库时系统会默认从/lib或/usr/lib路径下查找库文件不用-L指定路径了除了拷贝库到/lib目录下之外也可以在LD_LIBRARY_PATH环境变量中加上库所在路径。或者 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾并执行ldconfig刷新sudo vi xx.conf 添加动态库存在的路径使用绝对路径-L路径指定库的路径-l库名指定链接的库名-I(大写i) 路径指定头文件的路径 默认查找的路径/usr/include 代表从系统路径下查找 代表从当前路径下查找如果没有再去系统路径下查找ldd 可执行文件名查看链接的动态库3.4.总结静态库和动态库静态库编译阶段以.a结尾执行速度快体积大移植性好升级麻烦。动态库运行节点以.so结尾执行速度慢体积小移植性差升级简单。4. Linux IO模型阻塞IO、非阻塞IO、信号驱动IO了解、IO多路复用4.1. 阻塞IO最常见、效率低、不浪费CPU4.2. 非阻塞IO轮询、耗费CPU、可以同时处理多路IO设置文件描述符的属性设置非阻塞IOint flage fcntl(0, F_GETFL); // 1. 用flage接收fcntl函数的返回值文件描述符的属性信息 flage | O_NONBLOCK; // 2. 添加非阻塞权限 fcntl(0, F_SETFL, flage); // 3. 将修改好的权限重新设置flag ~O_NONBLOCK; fcntl(0, F_SETFL, flag);4.3. 信号驱动IO异步通知方式底层驱动支持异步通知是一种非阻塞的通知机制发送方发送通知后不需要等待接收方的响应或确认。通知发送后发送方可以继续执行其他操作而无需等待接收方处理通知。三步1. 通过信号方式当内核检测到设备数据后会主动给应用发送信号SIGIO。2. 应用程序收到信号后做异步处理即可。3. 应用程序需要把自己的进程号告诉内核并打开异步通知机制。//1.设置将文件描述符和进程号提交给内核驱动 //一旦fd有事件响应, 则内核驱动会给进程号发送一个SIGIO的信号 fcntl(fd,F_SETOWN,getpid()); //2.设置异步通知 int flags; flags fcntl(fd, F_GETFL); //获取原属性 flags | O_ASYNC; //给flags设置异步 O_ASUNC 通知 fcntl(fd, F_SETFL, flags); //修改的属性设置进去,此时fd属于异步 //3.signal捕捉SIGIO信号 --- SIGIO:内核会通知进程有新的IO信号可用 //一旦内核给进程发送sigio信号则执行handler signal(SIGIO,handler);总结阻塞IO(Blocking IO)非阻塞IO(Non-blocking IO)信号驱动IO(Signal-driven IO)同步性同步非同步异步描述调用IO操作的线程会被阻塞直到操作完成调用IO操作时如果不能立即完成操作会立即返回线程可以继续执行其他操作当IO操作可以进行时内核会发送信号通知进程特点最常见、效率低、不耗费cpu轮询、耗费CPU可以处理多路IO效率高异步通知方式需要底层驱动的支持适应场景小规模IO操作对性能要求不高高并发网络服务器减少线程阻塞时间实时性要求高的应用避免轮询开销4.4. IO多路复用4.4.1. select1特点1. 一个进程最多只能监听1024个文件描述符2. select被唤醒之后要重新轮询效率相对低3. select每次都会清空未发生响应的文件描述符每次拷贝都需要从用户空间到内核空间效率低开销大2编程步骤六步1. 先构造一张关于文件描述符的表2. 清空表 FD_ZERO3. 将关心的文件描述符添加到表中 FD_SET4. 调用select函数5. 判断是哪一个或者式哪些文件描述符产生了事件 FD_ISSET6. 做对应的逻辑处理3超时检测避免进程在没有数据时无限制的阻塞;select函数#include sys/time.h#include sys/types.h#include unistd.hint select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);功能实现IO的多路复用参数1. nfds关注的最大的文件描述符12. readfds关注的读表3. writefds关注的写表4. exceptfds关注的异常表5. timeout超时的设置返回值准备好的文件描述符的个数-1 失败0 超时检测时间到并且没有文件描述符准备好#include stdio.h #include stdlib.h #include sys/time.h #include sys/types.h #include sys/stat.h #include sys/select.h #include unistd.h #include fcntl.h #include string.h int main(int argc, char const *argv[]) { int ret 0; char buf[32] {}; int fd open(/dev/input/mouse0, O_RDONLY); if (fd 0) { perror(open err); return -1; } // 1.先构造一张关于文件描述符的表 fd_set rfds; while (1) { // 2.清空表 FD_ZERO FD_ZERO(rfds); // 3.将关心的文件描述符添加到表中 FD_SET FD_SET(fd, rfds); // 鼠标 FD_SET(0, rfds); // 键盘 struct timeval tm {2, 0}; // 4.调用select函数 ret select(fd 1, rfds, NULL, NULL, tm); if (ret 0) { perror(select err); return -1; } else if (ret 0) { printf(time out\n); continue; } // 5.判断是哪一个或者式哪些文件描述符产生了事件 FD_ISSET if (FD_ISSET(0, rfds)) { // 6.做对应的逻辑处理 fgets(buf, sizeof(buf), stdin); printf(buf: %s\n, buf); } if (FD_ISSET(fd, rfds)) { // 6.做对应的逻辑处理 read(fd, buf, sizeof(buf)); printf(mouse: %s\n, buf); } memset(buf, 0, sizeof(buf)); } close(fd); return 0; }4.4.2.poll1特点1. 优化文件描述符的限制文件描述符的限制取决于系统2. poll被唤醒之后要重新轮询一遍效率相对低3. poll不需要重新构造表采用结构体数组每次都需要从用户空间拷贝到内核空间2实现过程1. 创建一个表也就是一个结构体数组 struct pollfd fds[100];2. 将关心的描述符添加到表中并赋予事件3. 循环调用poll更新表while(1){poll();}4. 逻辑判断 if(fds[i].reventsPOLLIN) {}4.4.3.epoll特点1. 监听的最大的文件描述符没有个数限制2. 异步IOepoll当有事件产生被唤醒之后文件描述符主动调用callback函数回调函数直接拿到唤醒的文件描述符不需要轮询效率高3. epoll不需要重新构造文件描述符表只需要从用户空间拷贝到内核空间一次。IO多路复用总结selectpollepoll监听个数一个进程最多监听1024个文件描述符由程序员自己决定百万级方式每次都会被唤醒都需要重新轮询每次都会被唤醒都需要重新轮询红黑树内callback自动回调不需要轮询效率文件描述符数目越多轮询越多效率越低文件描述符数目越多轮询越多效率越低不轮询效率高原理每次使用select后都会清空表每次调用select都需要拷贝用户空间的表到内核空间内核空间负责轮询监视表内的文件描述符将发生事件的文件描述符拷贝到用户空间再次调用select如此循环不会清空结构体数组每次调用poll都需要拷贝用户空间的结构体到内核空间内核空间负责轮询监视结构体数组内的文件描述符将发生事件的文件描述符拷贝到用户空间再次调用poll如此循环不会清空表epoll中每个fd只会从用户空间到内核空间只拷贝一次上树时通过epoll_ctl将文件描述符交给内核监管一旦fd就绪内核就会采用callback的回调机制来激活该fdepoll_wait便可以收到通知内核空间到用户空间的拷贝特点一个进程最多能监听1024个文件描述符select每次被唤醒都要重新轮询表效率低select每次都清空未发生相应的文件描述符每次都要拷贝用户空间的表到内核空间优化文件描述符的个数限制poll每次被唤醒都要重新轮询效率比较低耗费cpupoll不需要构造文件描述符表也不需要清空表采用结构体数组每次也需要从用户空间拷贝到内核空间监听的文件描述符没有个数限制取决于自己的系统异步IOepoll当有事件产生被唤醒文件描述符会主动调用callback函数拿到唤醒的文件描述符不需要轮询效率高epoll不需要构造文件描述符的表只需要从用户空间拷贝到内核空间一次。结构数组数组红黑树就绪链表开发复杂度低低中5. 进程程序:编译好的可执行文件存放在磁盘上的指令和数据的有序集合(文件)程序是静态的没有任何执行的概念进程一个独立的可调度的任务执行一个程序所分配资源的总称进程是程序的一次执行过程进程是动态的包括创建、调度、执行和消亡特点1. 系统会为每个进程分配0-4g的虚拟空间其中0-3g是用户空间每个进程独有3g-4g是内核空间所有进程共享2. 轮转调度时间片系统为每个进程分配时间片(几毫秒~几十毫秒),当一个进程时间片用完时CPU调度另一个进程从而实现进程调度的切换 (没有外界干预是随机调度)5.1. 进程段Linux中的进程大致包含三个段数据段存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。正文段存放的是程序中的代码堆栈段存放的是函数的返回地址、函数的参数以及程序中的局部变量 类比内存的栈区5.2. 进程分类交互进程该类进程是由shell控制和运行的。交互进程既可以在前台运行也可以在后台运行。该类进程经常与用户进行交互需要等待用户的输入当接收到用户的输入后该类进程会立刻响应典型的交互式进程有shell命令进程、文本编辑器等批处理进程该类进程不属于某个终端它被提交到一个队列中以便顺序执行。(目前接触不到)守护进程该类进程在后台运行。它一般在Linux启动时开始执行系统关闭时才结束。5.3. 进程状态1运行态TASK_RUNNINGR指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。2睡眠态(等待态)可中断睡眠态TASK_INTERRUPTIBLES处于等待状态中的进程一旦被该进程等待的资源被释放那么该进程就会进入运行状态。(只能通过特定的函数进行唤醒是不能随便去中断的)不可中断睡眠态TASK_UNINTERRUPTIBLED该状态的进程只能用wake_up()函数唤醒。3暂停态TASK_STOPPED:T当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。4死亡态进程结束 X5僵尸态Z 当进程已经终止运行但还占用系统资源要避免僵尸态的产生 高优先级N 低优先级s 会话组组长l 多线程 前台进程5.4. 进程状态切换图进程创建后进程进入就绪态当CPU调度到此进程时进入运行态当时间片用完时此进程会进入就绪态如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态完成IO操作阻塞结束后又可进入就绪态等待CPU的调度当进程运行结束即进入结束态5.5. 进程相关命令ps 查看系统中的进程 -aux -eftop 动态显示系统的进程nice 按用户指定的优先级运行进程renice 改变正在运行进程的优先级kill 发送信号给进程jobs查看当前终端的后台进程bg 将进程切换到后台执行fg 将进程切换到前台执行5.6.进程函数5.6.1.创建进程 fork()#include sys/types.h #include unistd.h pid_t fork(void); 功能创建子进程 参数无 返回值 成功在父进程中返回子进程的进程号 0 在子进程中返回值为0 失败-1 并设置errno#include stdio.h #include sys/types.h #include unistd.h int main(int argc, char const *argv[]) { pid_t pid; pid fork(); if(pid 0) { perror(fork err); return -1; } // 当返回值为0相当于在子进程中运行 else if(pid 0) { printf(in then child\n); // while(1); } // 当返回值大于零的时候相当于在父进程中运行 else { printf(in the parent\n); } return 0; }解释./a.out会启动一个进程执行到fork()函数时会在当前进程中创造了一个子进程并把代码以及数据信息拷贝到子进程这两个进程只有个别数据例如进程号不一样此时这两个进程由CPU随机调度。注意!!子进程会得到fork函数返回值然后执行fork之后的代码fork函数之前的代码不会执行。特点1) 子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等但它们的PID、PPID是不同的。2) 父子进程有独立的地址空间互不影响当在相应的进程中改变全局变量、静态变量都互不影响。3) fork之前的代码会被复制但是不会被重新执行一遍fork之后的代码会被复制并且父子进程分别执行一遍。4) fork之前打开的文件fork之后会拿到同一个文件描述符操作同一个文件指针。5) 若父进程先结束子进程成为孤儿进程被init进程进程号1收养子进程变成后台进程。6) 若子进程先结束父进程如果没有及时回收子进程子进程变成僵尸进程要避免僵尸进程产生。5.6.2. 回收子进程资源#include sys/types.h #include sys/wait.h pid_t wait(int *wstatus); 功能回收子进程资源(阻塞) 参数wstatus子进程退出状态不接受子进程状态设为NULL 返回值成功回收的子进程的进程号 失败-1 pid_t waitpid(pid_t pid, int *wstatus, int options); 功能回收子进程资源 参数 pid 0 指定子进程进程号 -1 任意子进程 0 等待其组ID等于调用进程的组ID的任一子进程 -1 等待其组ID等于pid的绝对值的任一子进程 wstatus子进程退出状态 options0 阻塞 WNOHANG非阻塞 (没有子进程退出立刻返回) 返回值正常回收的子进程的进程号 当使用选项WNOHANG且没有子进程结束时0 失败-1#include stdio.h #include unistd.h #include sys/types.h #include sys/wait.h int main(int argc, char const *argv[]) { pid_t pid; pid fork(); if(pid 0) { perror(fork err); return -1; } // 当返回值为0相当于在子进程中运行 else if(pid 0) { sleep(2); // 让子进程等待一会结束 printf(in then child %d\n, pid); } // 当返回值大于零的时候相当于在父进程中运行 else { printf(in the parent %d\n, pid); // wait(NULL); // 回收子进程资源 // 0: 阻塞 // WNOHANG非阻塞有可能调用的时候子进程还没有结束回收不到资源还是会产生僵尸 // 需要轮询 // waitpid(-1, NULL, 0); while(1) { if(waitpid(-1, NULL, WNOHANG) 0) break; } } return 0; }5.6.3.结束进程#include stdlib.h void exit(int status); 功能结束进程刷新缓存 #include unistd.h void _exit(int status); 功能结束进程不刷新缓存 参数status是一个整型的参数可以利用这个参数传递进程结束时的状态。 通常0表示正常结束其他数值表示错误结束补充exit和return区别exit不管在子函数还是主函数都可以结束进程return当子函数中有return时返回到函数调用位置并不结束进程5.6.4.获取进程号#include sys/types.h #include unistd.h pid_t getpid(void); 功能获取当前进程的进程号 pid_t getppid(void); 功能获取当前进程的父进程号6. 进程间通信IPC进程间通信方式1) 早期的进程间通信无名管道(pipe)、有名管道(fifo)、信号(signal)2) system V IPC对象共享内存(share memory)、信号灯集(semaphore)、消息队列(message queue)3) BSDsocket套接字6.1. 无名管道6.1.1. 特点1) 只能用于具有亲缘关系的进程之间通信2) 具有固定的读端和写端半双工通信模式单工只能单向通信广播半双工可以双向通信但是同一时间不可以同时发送对讲机全双工可以双向同时通信电话3) 管道可以看成是一种特殊的文件对于它的读写可以使用文件IO如read、write函数4) 管道是基于文件描述符的通信方式。当一个管道建立时它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道而fd[1]固定用于写管道。6.1.2. 函数接口int pipe(int fd[2]) 功能创建无名管道 参数文件描述符fd[0]读端 fd[1]写端 返回值成功0 失败-16.2. 有名管道6.2.1. 特点1) 有名管道可以使互不相关的两个进程互相通信2) 有名管道可以通过路径名来指出并在文件系统中可见但内容存放在内存中。但是读写数据不会存在文件中而是在管道中。3) 进程通过文件IO来操作有名管道4) 有名管道遵循先进先出规则5) 不支持如lseek() 操作6.2.2. 函数接口int mkfifo(const char *filename, mode_t mode); 功能创建有名管道 参数filename有名管道文件名 mode权限 返回值成功0 失败-1并设置errno号#include stdio.h #include sys/types.h #include sys/stat.h #include errno.h int main(int argc, char const *argv[]) { // 创建有名管道 // fifo管道文件的权限值是664 // 因为我指定的是666 它和umask取反之后按位相与 if (mkfifo(fifo, 0666) 0) { // 如果返回的错误码等于EEXIST我并不希望我们的程序退出 if (errno EEXIST) { printf(file exist\n); } // 如果是其他的错误再让它打印错误信息并return else { perror(mkfifo error); return -1; } } printf(mkfifo success\n); return 0; }补充1. 当管道文件存在(报错提示file exists)时的处理方式判断errno的值为EEXIST时只是打印提示语句if(errno EEXIST)2. 注意代码中出现errno需要添加头文件#include errno.h注意函数只是在路径下创建管道文件往管道中写的数据是存在内核空间中的。步骤先创建有名管道mkfifo,然后再文件IO的open获取文件描述符之后才能读写read/write文件。6.3. 信号信号是进程通信方式中的唯一的一种异步的方式同步按照一定顺序去执行异步没有顺序的它不要求先后顺序它是来什么信号处理什么信号6.3.1. 概念● 信号是在软件层次上对中断机制的一种模拟是一种 异步通信方式● 信号可以直接进行用户空间进程和内核进程之间的交互内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。● 如果该进程当前并未处于执行态则该信号就由内核保存起来直到该进程恢复执行再传递给它如果一个信号被进程设置为阻塞则该信号的传递被延迟直到其阻塞被取消时才被传递给进程。、6.3.2. 信号的响应方式忽略信号对信号不做任何的处理但是有两个信号不能忽略即SIGKILL、SIGSTOP捕捉信号定义信号处理函数当信号发生时执行相应的处理函数执行默认(缺省)操作Linux对每种信号都规定了默认操作6.3.3. 信号种类SIGINT2中断信号Ctrl-C 产生用于中断进程SIGQUIT3退出信号 Ctrl\ 产生用于退出进程并生成核心转储文件SIGKILL9终止信号用于强制终止进程。此信号不能被捕获或忽略。SIGALRM14闹钟信号当由 alarm() 函数设置的定时器超时时产生。SIGTERM15终止信号用于请求终止进程。此信号可以被捕获或忽略。terminationSIGCHLD17子进程状态改变信号当子进程停止或终止时产生。SIGCONT18继续执行信号用于恢复先前停止的进程。SIGSTOP19停止执行信号用于强制停止进程。此信号不能被捕获或忽略。SIGTSTP20键盘停止信号通常由用户按下 Ctrl-Z 产生用于请求停止进程。6.3.4. 函数接口6.3.4.1.1.信号发送和挂起#include sys/types.h #include signal.h int kill(pid_t pid, int sig); 功能信号发送 参数pid指定的进程 sig要发送的信号 返回值成功0 失败-1 #include signal.h int raise(int sig); 功能进程向自己发送信号 参数sig信号 返回值成功0 失败-1 #include unistd.h int pause(void); 功能用于将调用进程挂起直到收到被捕获处理的信号为止#include sys/types.h #include signal.h #include unistd.h int main(int argc, char const *argv[]) { // kill(getpid(), SIGKILL); // raise(SIGKILL); // while(1); pause(); // 将进程挂起作用和死循环类似但是不占用CPU return 0; }6.3.4.1.2.定时器#include unistd.h unsigned int alarm(unsigned int seconds); 功能在进程中设置一个定时器当定时器指定的时间到了会向进程发送SIGALRM信号 参数seconds定时时间 单位秒s 返回值 如果调用此alarm()前进程中已经设置了闹钟时间则返回上一个闹钟时间的剩余时间否则返回0。 注意一个进程只能有一个闹钟时间。如果在调用alarm时 已设置过闹钟时间则之前的闹钟时间被新值所代替 常用操作取消定时器alarm(0)返回旧闹钟余下秒数#include stdio.h #include sys/types.h #include signal.h #include unistd.h int main(int argc, char const *argv[]) { printf(%d\n, alarm(10)); // 第一次调用返回 0 sleep(2); printf(%d\n, alarm(3)); // 不是第一次调用返回上一次闹钟剩余的时间 pause(); // 让进程不结束等待闹钟 // linux 系统对SIGALRM默认处理方案就是结束进程 return 0; }6.3.4.1.3.信号处理函数 signal#include signal.h typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 功能信号处理函数 参数signum要处理的信号 handler信号处理方式 SIG_IGN忽略信号 (忽略 ignore SIG_DFL执行默认操作 默认 default handler捕捉信号 (handler为函数名可以自定义) void handler(int sig){} //函数名可以自定义, 参数为要处理的信号 返回值成功设置之前的信号处理方式 失败-1#include stdio.h #include sys/types.h #include signal.h #include unistd.h void handler(int sig) { printf(handler:%d\n, sig); } int main(int argc, char const *argv[]) { // signal(SIGINT, SIG_IGN); // 忽略信号 // signal(SIGINT, SIG_DFL); // 执行默认信号 signal(SIGINT, handler); while(1); return 0; }用信号的知识实现司机和售票员问题。1售票员捕捉SIGINT代表开车信号向司机发送SIGUSR1信号司机打印lets gogogo2售票员捕捉SIGQUIT代表停车信号向司机发送SIGUSR2信号司机打印stop the bus3司机捕捉SIGTSTP代表到达终点站信号向售票员发送SIGUSR1信号售票员打印please get off the bus4司机等待售票员下车之后司机再下车。分析司机(父进程)、售票员(子进程)售票员捕捉SIGINT、SIGQUIT、SIGUSR1忽略SIGTSTP司机捕捉SIGUSR1、SIGUSR2、SIGTSTP忽略SIGINT、SIGQUIT#include stdio.h #include sys/types.h #include sys/wait.h #include signal.h #include unistd.h #include stdlib.h pid_t pid; void handler(int sig)//子进程接收信号执行的函数 { if (sig SIGINT) kill(getppid(), SIGUSR1); if (sig SIGQUIT) kill(getppid(), SIGUSR2); if (sig SIGUSR1) { printf(please get off the bus\n); exit(0); } } void driver(int sig)//父进程接收信号执行的函数 { if (sig SIGUSR1) printf(lets gogogo\n); if (sig SIGUSR2) printf(stop the bus\n); if (sig SIGTSTP) { kill(pid, SIGUSR1); wait(NULL); exit(0); } } int main(int argc, char const *argv[]) { pid fork(); if (pid 0) { perror(fork err); return -1; } // 当返回值为0相当于在子进程中运行 else if (pid 0) { signal(SIGTSTP, SIG_IGN); signal(SIGINT, handler); signal(SIGQUIT, handler); signal(SIGUSR1, handler); } // 当返回值大于零的时候相当于在父进程中运行 else { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGUSR1, driver); signal(SIGUSR2, driver); signal(SIGTSTP, driver); } while (1) pause(); return 0; }6.4. 共享内存共享内存指的是操作系统在物理内存中申请一块空间应用程序可以映射到这块空间进行直接读写操作特点1共享内存是一种最为高效的进程间通信方式进程可以直接读写内存而不需要任何数据的拷贝2为了在多个进程间交换信息内核专门留出了一块内存区可以由需要访问的进程将其映射到自己的私有地址空间3进程就可以直接读写这一内存区而不需要进行数据的拷贝从而大大提高的效率。4由于多个进程共享一段内存因此也需要依靠某种同步机制如互斥锁和信号量等6.4.1. 步骤1. 创建唯一key值 ftok2. 创建或打开共享内存 shmget3. 映射共享内存到用户空间(拿到映射的地址后就可以操作共享内存) shmat4. 撤销映射 shmdt5. 删除共享内存 shmctl6.4.2. 函数接口#include sys/types.h #include sys/ipc.h key_t ftok(const char *pathname, int proj_id);//创建唯一key值 #include sys/shm.h int shmget(key_t key, size_t size, IPC_CREAT | IPC_EXCL | 0666);//创建或打开共享内存 //两种失败情况errnoEEXIST时shmidshmget(key,1024,0666) #include sys/shm.h void *shmat(int shmid, const void *shmaddr, int shmflg);//映射共享内存到用户空间 #include sys/shm.h int shmdt(const void *shmaddr);//撤销映射 #include sys/shm.h int shmctl(int shmid, int cmd, struct shmid_ds *buf);// 删除共享内存#include sys/types.h #include sys/ipc.h key_t ftok(const char *pathname, int proj_id); 功能创建key值 参数: pathname: 文件名 proj_id取整数的低8位数据 返回值成功key值 失败-1补充key值是根据pathname的inode号和proj_id的低8位组合而成的。如0x61013096pathname只要是路径中存在的文件即可ls -i 查看文件inode号6.4.2.1.创建共享内存#include sys/shm.h int shmget(key_t key, size_t size, int shmflg); 功能创建或打开共享内存 参数key 键值 size共享内存的大小 创建 检测错误 shmflgIPC_CREAT | IPC_EXCL | 0777 创建共享内存时候的权限 返回值成功shmid 共享内存的id 出错-1查看创建的共享内存的命令ipcs -m6.4.2.2.映射共享内存#include sys/shm.h void *shmat(int shmid, const void *shmaddr, int shmflg); 功能映射共享内存即把指定的共享内存映射到进程的地址空间用于访问 参数shmid共享内存的id号 shmadd一般为NULL表示由系统自动完成映射 如果不为NULL那么由用户指定 shmflgSHM_RDONLYH就是对该共享内存进行只读操作 0 可读可写 返回值成功完成映射后的地址 出错(void *)-1的地址6.4.2.3.取消映射#include sys/shm.h int shmdt(const void *shmaddr); 功能取消映射 参数shmaddr要取消映射的共享内存地址 返回值成功0 失败-16.4.2.4.删除共享内存#include sys/shm.h int shmctl(int shmid, int cmd, struct shmid_ds *buf); 功能(删除共享内存), 对共享内存进行各种操作 参数shmid 共享内存id cmd IPC_STAT 获取shmid属性信息存放在第三个参数 IPC_SET设置shmid属性信息要设置的属性存放在第三个参数 IPC_RMID删除共享内存此时第三个参数为NULL buf 是一个结构体指针但是我们是删除共享内存所以没有意义我们直接设置为NULL就可以 返回值成功 0 失败 -16.4.3.操作命令ipcs -m: 查看系统中的共享内存ipcrm -m shmid删除共享内存ps: 可能不能直接删除掉还存在进程使用的共享内存。这时候可以用ps -ef对进程进行查看kill掉多余的进程后再使用ipcs查看。实例#include stdio.h #include sys/types.h #include sys/ipc.h #include sys/shm.h #include errno.h #include string.h int main(int argc, char const *argv[]) { key_t key; int shmid; // 共享内存id key ftok(./app, a); if (key 0) { perror(ftok err); } printf(%#x\n, key); // 创建共享内存 shmid shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666); if (shmid 0) { if (errno EEXIST) { shmid shmget(key, 64, 0666); } else { perror(shmget err); return -1; } } printf(%d\n, shmid); // 映射共享内存 // 让系统完成映射 char *p shmat(shmid, NULL, 0); if(p (char *)-1) { perror(shmat err); return -1; } while(1) { scanf(%s, p); if(!strcmp(p, quit)) break; } return 0; }#include stdio.h #include sys/types.h #include sys/ipc.h #include sys/shm.h #include errno.h #include string.h int main(int argc, char const *argv[]) { key_t key; int shmid; // 共享内存id key ftok(./app, a); if (key 0) { perror(ftok err); } printf(%#x\n, key); // 创建共享内存 shmid shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666); if (shmid 0) { if (errno EEXIST) { shmid shmget(key, 64, 0666); } else { perror(shmget err); return -1; } } printf(%d\n, shmid); // 映射共享内存 // 让系统完成映射 char *p shmat(shmid, NULL, 0); if(p (char *)-1) { perror(shmat err); return -1; } while(1) { if(!strcmp(p, quit)) break; printf(p:%s\n, p); } // 使用完成之后需要取消映射 shmdt(p); // 删除共享内存 shmctl(shmid, IPC_RMID, NULL); return 0; }6.5.信号灯集6.5.1.特点信号灯(semaphore)也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制System V信号灯集是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。通过信号灯集实现共享内存的同步操作6.5.2.步骤1. 创建key值ftok2. 创建或打开信号灯集: semget3. 初始化信号灯: semctl4. PV操作semop5. 删除信号灯集: semctl6.5.3.操作命令ipcs -s查看信号灯集ipcrm -s semid删除信号灯集6.5.4.函数接口6.5.4.1.创建信号灯集#include sys/sem.h int semget(key_t key, int nsems, int semflg); 功能创建/打开信号灯 参数keyftok产生的key值 nsems信号灯集中包含的信号灯数目 semflg信号灯集的访问权限通常为IPC_CREAT | 0666 返回值成功信号灯集ID 失败-1semid等于0时我们认为是错误的可以手动去删除这个信号灯集ipcs -s 查看创建的信号灯集ipcrm -s [semid]删除信号灯集6.5.4.2.初始化或删除信号灯集#include sys/sem.h int semctl(int semid, int semnum, int cmd, ...); 功能信号灯集的控制(初始化、删除) 参数semid信号灯集id semnum要操作集合中的信号灯编号 cmd GETVAL获取信号灯的值 SETVAL设置信号灯的值 IPC_RMID从系统中删除信号灯集合 ...当cmd为SETVAL需要传递共用体 返回值成功 0 失败 -1 共用体格式 union semun { int val; /* 信号量的初值 */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ };补充1. 当cmd为SETVAL时需要传递第四个参数类型为共用体用法union semun { int val; }; union semun sem; sem.val 10; semctl(semid, 0, SETVAL, sem); //对编号为0的信号灯设置初值为101. 当cmd为IPC_RMID时表示删除信号灯集用法semctl(semid, 0, IPC_RMID) // 0表示信号灯的编号指定任意一个即可删除2. 当cmd为GETVAL时表示获取信号灯的值用法printf(%d\n, semctl(semid, 0, GETVAL));6.5.4.3.pv操作int semop ( int semid, struct sembuf *opsptr, size_t nops); 功能对信号灯集合中的信号量进行PV操作 参数semid信号灯集ID opsptr:操作方式 nops: 要操作的信号灯的个数 1个 返回值成功 0 失败-1 struct sembuf { short sem_num; // 要操作的信号灯的编号 short sem_op; // 0 : 等待直到信号灯的值变成0 // 1 : 释放资源V操作 // -1 : 分配资源P操作 short sem_flg; // 0阻塞,IPC_NOWAIT, SEM_UNDO };使用申请资源 P操作mysembuf.sem_num 0;mysembuf.sem_op -1;mysembuf.sem_flg 0;semop(semid,mysembuf,1);释放资源 V操作mysembuf.sem_num 0;mysembuf.sem_op 1;mysembuf.sem_flg 0;semop(semid,mysembuf,1);实例#include stdio.h #include sys/types.h #include sys/ipc.h #include sys/sem.h #include errno.h union semun { int val; }; int main(int argc, char const *argv[]) { int semid; key_t key; key ftok(./app, a); if (key 0) { perror(ftok error); return -1; } // 创建信号灯集 semid semget(key, 2, IPC_CREAT | IPC_EXCL | 0666); if (semid 0) { if (errno EEXIST) semid semget(key, 2, 0666); else { perror(semget error); return -1; } } else { // 初始化(只需要在创建时进行初始化即可) // 我们初始化的操作执行一次就可以了避免重复的初始化 union semun sem; sem.val 10; semctl(semid, 0, SETVAL, sem); // 对编号为0的信号灯设置初值为10 sem.val 0; semctl(semid, 1, SETVAL, sem); // 对编号为1的信号灯设置初值为0 } printf(%d\n, semid); // 获取信号灯的值 printf(%d\n, semctl(semid, 0, GETVAL)); printf(%d\n, semctl(semid, 1, GETVAL)); // p操作(申请资源) struct sembuf buf {0, -1, 0}; // 编号为0的信号灯进行p操作(申请资源) semop(semid, buf, 1); // v操作(释放资源) buf.sem_num 1; // 编号为1的信号灯进行v操作(释放资源) buf.sem_op 1; buf.sem_flg 0; semop(semid, buf, 1); // 获取信号灯的值 printf(%d\n, semctl(semid, 0, GETVAL)); printf(%d\n, semctl(semid, 1, GETVAL)); // 删除信号灯集 semctl(semid, 0, IPC_RMID); return 0; }6.6.消息队列6.6.1.特点消息队列是IPC对象的一种(活动在内核级别的一种进程间通信的工具)1. 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。2. 消息队列可以按照类型来发送/接收消息3. 在linux下消息队列的大小有限制。● 消息队列个数最多为16个● 消息队列总容量最多为16384字节● 每个消息内容最多为8192字节。消息是通过链表的方式依次进行添加可以通过类型来区分添加的是那种类型的数据同种类型的数据在读取的时候是按照队列的方式读取的不同类型的数据是按照类型进行读取6.6.2.步骤1. 创建key值2. 创建或打开消息队列 msgget (message deque)3. 添加消息 msgsnd (send)4. 读取消息 msgrcv (recive)5. 删除消息队列 msgctl6.6.3.操作命令ipcs -q: 查看消息队列ipcrm -q msgid: 删除消息队列注意有时候可能创建失败或者msgid为0所以用命令看看删了重新创建就可以了。6.6.4.函数接口int msgget(key_t key, int flag); 功能创建或打开一个消息队列 参数 key值 flag创建消息队列的权限IPC_CREAT|IPC_EXCL|0666 返回值成功msgid 失败-1 int msgsnd(int msgid, const void *msgp, size_t size, int flag); 功能添加消息 参数msgid消息队列的ID msgp指向消息的指针。常用消息结构msgbuf如下 struct msgbuf{ long mtype; //消息类型 值0 char mtext[N]} //消息正文 } size发送的消息正文的字节数 flagIPC_NOWAIT消息没有发送完成函数也会立即返回 0直到发送完成函数才返回 返回值成功0 失败-1 使用msgsnd(msgid, msg,sizeof(msg)-sizeof(long), 0) 注意消息结构除了第一个成员必须为long类型外其他成员可以根据应用的需求自行定义。 int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag); 功能读取消息 参数msgid消息队列的ID msgp存放读取消息的空间 size接受的消息正文的字节数(sizeof(msgp)-sizeof(long)) msgtype 0接收消息队列中第一个消息。 大于0接收消息队列中第一个类型为msgtyp的消息. 小于0接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。 flag 0若无消息函数会一直阻塞 IPC_NOWAIT若没有消息进程会立即返回ENOMSG 返回值成功接收到的消息的长度 失败-1 int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); 功能对消息队列的操作删除消息队列 参数msqid消息队列的队列ID cmd IPC_STAT读取消息队列的属性并将其保存在buf指向的缓冲区中。 IPC_SET设置消息队列的属性。这个值取自buf参数。 IPC_RMID从系统中删除消息队列。 buf消息队列缓冲区 返回值成功0 失败-1 用法msgctl(msgid, IPC_RMID, NULL);#include stdio.h #include sys/msg.h #include sys/types.h #include sys/ipc.h #include errno.h #include string.h struct msgbuf { long mtype; // 消息类型 char ch[32]; // 消息正文 int n; }; int main(int argc, char const *argv[]) { int msgid; key_t key; key ftok(./app, a); if (key 0) { perror(ftok err); } // 创建消息队列 msgid msgget(key, IPC_CREAT | IPC_EXCL | 0666); if (msgid 0) { if (errno EEXIST) { msgid msgget(key, 0666); } else { perror(msgget err); return -1; } } printf(msgid: %d\n, msgid); // 添加消息 struct msgbuf msg; msg.mtype 1; strcpy(msg.ch, hello); msg.n10; msgsnd(msgid, msg, sizeof(msg)-sizeof(long), 0); msg.mtype 2; strcpy(msg.ch, world); msg.n20; msgsnd(msgid, msg, sizeof(msg)-sizeof(long), 0); msg.mtype 2; strcpy(msg.ch, hahah); msg.n300; msgsnd(msgid, msg, sizeof(msg)-sizeof(long), 0); // 读取消息 struct msgbuf m; msgrcv(msgid, m, sizeof(m)-sizeof(long), 2, 0); printf(%s %d\n, m.ch, m.n); msgrcv(msgid, m, sizeof(m)-sizeof(long), 2, 0); printf(%s %d\n, m.ch, m.n); // 删除消息队列 msgctl(msgid, IPC_RMID, NULL); return 0; }7.线程 Thread7.1.概念线程是一个轻量级的进程为了提高系统的性能引入线程。线程和进程都参与统一的调度。在同一个进程中可以创建多个线程并且共享进程资源。7.2.进程和线程区别(面试题)相同点都为操作系统提供了并发执行的能力不同点资源和调度进程是系统资源分配的最小单位线程是资源调度的最小单位地址空间方面每个进程都有独立的地址空间同一个进程中的多个线程共享进程地址空间通信方面线程通信相对简单只需要通过全局变量就可以实现但是需要考虑临界资源访问的问题; 进程通信比较复杂需要借助进程间的通信机制(3-4g的内核空间)。安全性方面线程安全性差一些当进程结束时会导致所有线程退出; 进程相对安全。面试程序什么时候该使用线程什么时候用进程深圳棱镜空间智能科技有限公司、北京明朝万达对资源的管理和保护要求高不限制开销和效率时使用多进程。要求效率高、速度快的高并发环境时需要频繁创建、销毁或切换时资源的保护管理要求不是很高时使用多线程。7.3.线程资源共享的资源可执行的指令、静态数据、进程中打开的文件描述符、信号处理函数、当前工作目录、用户ID、用户组ID私有的资源线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈(局部变量, 返回地址)、错误号 (errno)、信号掩码和优先级、执行状态和属性7.4.函数接口编译的时候需要加 -lpthread 链接动态库7.4.1.创建线程pthread_create#include pthread.h int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg); 功能创建线程 参数thread: 线程标识 attr线程属性NULL代表设置默认属性 start_routine函数名代表线程函数(自己写) arg用来给前面函数传参 返回值成功0 失败错误码7.4.2.退出线程pthread_exit#include pthread.h void pthread_exit(void *retval); 功能用于退出线程的执行 参数retval:线程退出时返回的值线程结束之后不是多线程状态了此时只剩下一个主线程7.4.3.回收线程资源#include pthread.h int pthread_join(pthread_t thread, void **retval); 功能用于等待一个指定的线程结束阻塞函数 参数thread创建的线程对象线程ID retval指针*value_ptr指向线程返回的参数一般为NULL 返回值成功0 失败errno int pthread_detach(pthread_t thread); 功能让线程结束时自动回收线程资源,让线程和主线程分离,非阻塞函数 参数thread线程ID 非阻塞式的例如主线程分离(detach)了线程T2那么主线程不会阻塞在pthread_detach() pthread_detach()会直接返回线程T2终止后会被操作系统自动回收资源练习输入输出quit结束通过线程实现数据的交互主线程循环从终端输入线程函数将数据循环输出当输入quit结束程序。1) 全局变量2) 加上标志位(flag)实现主线程输入一次修改标志位从线程打印一次也修改标志位 int flag0;#include stdio.h #include pthread.h #include unistd.h #include string.h int flag 0; // 全局标志 char buf[32] {}; void *handler(void *arg) { while (1) { if (flag 1) // 有新数据要打印 { printf(%s\n, buf); flag 0; // 打印后重置标志 } else if (flag 2) { pthread_exit(NULL); // 线程退出 } } return NULL; } int main() { pthread_t tid; if (pthread_create(tid, NULL, handler, NULL) ! 0) { perror(线程创建失败); return 1; } while (1) { scanf(%s, buf); if (strcmp(buf, quit) 0) { flag 2; // 退出 break; } flag 1; // 标记有新数据 } pthread_join(tid, NULL); return 0; }7.5. 线程同步7.5.1.概念多个线程(任务)按照约定的顺序相互配合完成一件事情7.5.2.同步机制通过信号量实现线程同步信号量通过信号量实现同步操作 由信号量来决定线程继续运行还是阻塞等待。信号量代表一类资源其值可以表示系统中该资源的数量。信号量的值0 表示有资源可以用可以申请到资源。信号量的值0: 表示没有资源可以用无法申请到资源阻塞。信号量还是受保护的变量只能通过三种操作来访问初始化P操作(申请资源)V操作释放资源sem_init信号量初始化sem_wait申请资源P操作如果没有资源可用则阻塞否则就申请到资源 -1sem_post释放资源V操作非阻塞1int sem_init(sem_t *sem, int pshared, unsigned int value) 功能初始化信号量 参数sem初始化的信号量对象 pshared信号量共享的范围(0: 线程间使用 非0:1进程间使用) value信号量初值 返回值成功 0 失败 -1 int sem_wait(sem_t *sem) 功能申请资源P操作减少信号量值 参数sem信号量对象 返回值成功 0 失败 -1 注此函数执行过程当信号量的值大于0时表示有资源可以用则继续执行同时对信号量减1 当信号量的值等于0时表示没有资源可以使用函数阻塞 int sem_post(sem_t *sem) 功能释放资源V操作增加信号量值 参数sem信号量对象 返回值成功 0 失败 -1 注释放一次信号量的值加1函数不阻塞练习通过信号量实现线程同步主线程循环从终端输入字符串子线程循环将字符串打印至终端当输入quit时结束#include stdio.h #include pthread.h #include unistd.h #include string.h #include semaphore.h sem_t sem; sem_t sem1; char buf[32] {}; void *handler(void *arg) { while (1) { sem_wait(sem); if (!strcmp(buf, quit)) break; printf(%s\n, buf); sem_post(sem1); } return NULL; } int main() { pthread_t tid; if (pthread_create(tid, NULL, handler, NULL) ! 0) { perror(线程创建失败); return 1; } if (sem_init(sem, 0, 0) 0) { perror(sem init err); return -1; } if (sem_init(sem1, 0, 1) 0) { perror(sem1 init err); return -1; } while (1) { sem_wait(sem1); scanf(%s, buf); sem_post(sem); if (strcmp(buf, quit) 0) { break; } } pthread_join(tid, NULL); return 0; }7.6.线程互斥7.6.1.互斥概念多个线程访问临界资源时同一时间只能一个线程访问临界资源多个线程共同访问的数据且一次仅允许一个线程所使用的资源通过互斥锁可以实现互斥机制主要用来保护临界资源每个临界资源都由一个互斥锁来保护线程必须先获得互斥锁才能访问临界资源访问完资源后释放该锁。如果无法获得锁线程会阻塞直到获得锁为止。互斥锁的操作方式初始化申请锁(上锁) 阻塞当申请不到锁时(表示锁被其它线程占用)是阻塞的释放锁(解锁) 非阻塞注意上锁和解锁需要成对存在7.6.2.函数接口int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) 功能初始化互斥锁 参数mutex互斥锁 attr: 互斥锁属性 // NULL表示缺省属性 返回值成功 0 失败 -1 int pthread_mutex_lock(pthread_mutex_t *mutex) 功能申请互斥锁 参数mutex互斥锁 返回值成功 0 失败 -1 注和pthread_mutex_trylock区别pthread_mutex_lock是阻塞的 pthread_mutex_trylock不阻塞如果申请不到锁会立刻返回 int pthread_mutex_unlock(pthread_mutex_t *mutex) 功能释放互斥锁 参数mutex互斥锁 返回值成功 0 失败 -1 int pthread_mutex_destroy(pthread_mutex_t *mutex) 功能销毁互斥锁 参数mutex互斥锁案例两个线程一个线程倒置全局数组中的数另一个线程遍历数组中数据,每隔1s打印一次。int a[10] {1,2,3,4,5,6,7,8,9,0};#include stdio.h #include pthread.h #include unistd.h int arr[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; pthread_mutex_t lock; void *swap_handler(void *arg) { int t; while(1) { pthread_mutex_lock(lock); for(int i 0; i 5; i) { t arr[i]; arr[i] arr[9-i]; arr[9-i] t; } pthread_mutex_unlock(lock); } } void *print_handler(void *arg) { while(1) { pthread_mutex_lock(lock); for(int i 0; i 9; i) { printf(%d , arr[i]); } printf(\n); pthread_mutex_unlock(lock); sleep(1); } } int main(int argc, char const *argv[]) { pthread_t tid1, tid2; if (pthread_create(tid1, NULL, swap_handler, NULL) ! 0) { perror(create thread err); } if (pthread_create(tid2, NULL, print_handler, NULL) ! 0) { perror(create thread err); } if(pthread_mutex_init(lock, NULL) ! 0) { perror(mutex init err); return -1; } pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }7.6.3. 补充死锁是指两个或两个以上的进程或线程在执行过程中由于竞争资源或者由于彼此通信而造成的一种阻塞的现象若无外力作用它们都将无法推进下去。死锁产生的四个必要条件1、互斥使用即当资源被一个线程使用(占有)时别的线程不能使用2、不可抢占资源请求者不能强制从资源占有者手中夺取资源资源只能由资源占有者主动释放。3、请求和保持即当资源请求者在请求其他的资源的同时保持对原有资源的占有。4、 循环等待即存在一个等待队列P1占有P2的资源P2占有P3的资源P3占有P1的资源。这样就形成了一个等待环路。注意当上述四个条件都成立的时候便形成死锁。当然死锁的情况下如果打破上述任何一个条件便可让死锁消失。7.6.4. 条件变量条件变量(cond)用于在线程之间传递信号以便某些线程可以等待某些条件发生。当某些条件发生时条件变量会发出信号使等待该条件的线程可以恢复执行。函数接口一般和互斥锁搭配使用来实现同步机制pthread_cond_init(cond,NULL); //初始化条件变量使用前需要上锁pthread_mutex_lock(lock); //上锁一些逻辑pthread_cond_wait(cond, lock); //阻塞等待条件产生没有条件产生时阻塞同时解锁当条件产生时结束阻塞再次上锁。执行线程里面的逻辑pthread_mutex_unlock(lock); / /解锁pthread_cond_signal(cond); //产生条件不阻塞pthread_cond_destroy(cond); //销毁条件变量注意必须保证让pthread_cond_wait先执行pthread_cond_sginal再产生条件。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

在线网站做成app做微商网站

深入了解Korn Shell的流程控制与调试工具 1. Korn Shell中的select结构 在Korn Shell里, select 是一种独特的流程控制结构,它在传统编程语言中并无类似物。 select 能让用户轻松生成简单的菜单。其语法如下: select name [in list] dostatements that can use $nam…

张小明 2026/3/10 16:54:05 网站建设

简约手机网站源码计算机软件工程师证怎么考

绕过微信(或类似的垄断级Super App)的核心逻辑,绝对不是“做一个更好的微信”,而是让“发消息”这个动作本身变得过时或次要。 老牌业务的护城河在于网络效应(所有人都在这里)和路径依赖(习惯&a…

张小明 2026/3/10 16:54:07 网站建设

嘉兴网站搜索排名知名品牌策划公司

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个VSCode小说阅读插件,使用NLP技术实现以下功能:1.自动识别小说章节结构并生成目录树 2.提取主要角色并生成关系图谱 3.提供章节内容智能摘要 4.支持自…

张小明 2026/3/10 16:54:09 网站建设

网站打开不了怎样做wordpress预览doc

急诊病例:当沙盒管理遭遇"老年痴呆" 【免费下载链接】Sandboxie Sandboxie Plus & Classic 项目地址: https://gitcode.com/gh_mirrors/sa/Sandboxie 患者主诉:"医生,我的Sandboxie-Plus最近像得了老年痴呆一样&am…

张小明 2026/3/10 16:54:11 网站建设

网站设计报价怎么做logo设计在线生成免费版

WebAssembly反编译实战:从二进制迷雾到清晰代码的蜕变之旅 【免费下载链接】wabt The WebAssembly Binary Toolkit 项目地址: https://gitcode.com/gh_mirrors/wa/wabt 为什么WebAssembly代码需要"翻译官"?🔍 当你面对一个…

张小明 2026/3/10 16:58:44 网站建设

网站做301跳转需解析童子营网站建设方案

第一章:自主学习AI的崛起与企业战略转型 随着深度学习与强化学习技术的不断突破,自主学习AI正逐步从实验室走向产业核心。这类系统能够在没有显式编程的情况下,通过环境交互和反馈机制自主优化决策策略,推动企业在智能制造、金融风…

张小明 2026/3/10 16:58:46 网站建设