一、线程的同步互斥机制
1.1 引入目的
- 由于多个线程共用进程的资源,所有可以通过全局资源完成多个进程的之间消息的传递
- 临界资源:多个线程共同使用的全局资源称为临界资源
- 临界区:访问临界资源的代码段称为临界区
- 多个进程访问临界资源时,会产生相互抢占的现象,该线程称为竞态,为了防止竞态的产生,引入了同步互斥机制
- 互斥:同一时刻,只允许一个线程使用临界资源,其他线程处于等待状态,直到拥有临界资源的线程释放了临界资源的使用权,没有先后顺序
- 同步:多个线程有顺序的访问临界资源
1.2 互斥
- 互斥机制中引入了互斥锁
- 互斥锁的本质也是一个特殊的临界资源,当某个线程抢到该锁资源后,其他线程只能处于等待状态,直到该线程释放锁资源
- 对于互斥锁的操作分别为:创建互斥锁、获取锁资源、释放锁资源、销毁互斥锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <pthread.h> 1、创建互斥锁,定义一个互斥锁变量
pthread_mutex_t mutex; 2、动态初始化互斥锁 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); 功能:初始化锁资源 参数1:互斥锁地址 参数2:互斥锁的属性,一般填NULL 返回值:总是返回0 3、上锁 int pthread_mutex_lock(pthread_mutex_t *mutex); 功能:给临界区上锁 参数:互斥锁的地址 返回值:成功返回0,失败返回错误码 4、解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); 功能:释放锁资源的使用 参数:互斥锁地址 返回值:成功返回0,失败返回错误码
5、销毁锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); 功能:销毁锁资源 参数:互斥锁地址 返回值:成功返回0,失败返回错误码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include<myhead.h> char buf[128];
pthread_mutex_t mutex;
void *task(void *arg) { while(1) { pthread_mutex_lock(&mutex); printf("分支线程中:buf = %s\n", buf); strcpy(buf, "I love China\n"); pthread_mutex_unlock(&mutex); } } int main(int argc, const char *argv[]) { pthread_t tid; pthread_mutex_init(&mutex, NULL); if(pthread_create(&tid, NULL, task, NULL) != 0) { printf("tid create error\n"); return -1; } while(1) { pthread_mutex_lock(&mutex); printf("主线程中buf = %s\n", buf); strcpy(buf, "hello world\n"); pthread_mutex_unlock(&mutex); } pthread_join(tid, NULL); pthread_mutex_destroy(&mutex); return 0; }
|
1.3 同步机制之无名信号量
- 同步:多个线程有顺序的执行
- 无名信号量:本质上也是一个临界资源,维护了一个value值,每个线程要执行时,先申请该value值,申请成功时,将value值减1,如果申请时,value值为0,则该线程在申请处阻塞,直到另一个线程将该无名信号量的value值增加到大于0.
- 线程同步多用于生产者消费者模型:生生产者生产数据,消费者消费数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <semaphore.h>
1. 初始化信号量 sem_t semaphore; sem_init 函数用于对信号量进行动态初始化 int sem_init(sem_t *sem, int pshared, unsigned int value); 参数: - sem: 指向信号量变量的指针 - pshared: 表示信号量是否应在进程之间共享的标志(对于线程为0,对于进程为非零) - value: 信号量的初始值 返回值:成功返回0,失败返回-1 示例: sem_init(&semaphore, 0, 1);
2. 等待(减少)信号量 int sem_wait(sem_t *sem); 功能:减少信号量的值。如果值大于0,则立即减少并立即返回。 如果值为0,则该函数阻塞,直到信号量的值大于0。 参数: - sem: 指向信号量变量的指针 返回值:成功返回0,失败返回-1 示例: sem_wait(&semaphore);
3. 发送(增加)信号量 int sem_post(sem_t *sem); 功能:增加信号量的值。如果有等待的线程,则其中一个被解除阻塞。 参数: - sem: 指向信号量变量的指针 返回值:成功返回0,失败返回-1 示例: sem_post(&semaphore);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include<myhead.h>
sem_t sem;
void *task1(void *arg) { while(1) { sleep(2); printf("我生产了一辆特斯拉\n"); sem_post(&sem); } }
void *task2(void *arg) { while(1) { sem_wait(&sem); printf("我消费了一辆特斯拉\n"); } }
int main(int argc, const char *argv[]) { pthread_t tid1,tid2; sem_init(&sem, 0, 0); if(pthread_create(&tid1, NULL, task1, NULL) != 0) { printf("tid1 create error\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0) { printf("tid2 create error\n"); return -1; } pthread_join(tid1, NULL); pthread_join(tid2, NULL); sem_destroy(&sem); return 0; }
|
1.4 同步机制之条件变量
1> 条件变量本质上也是一个临界资源,维护了一个队列,当消费者要想指向前,先进入等待队列中,直到生产者唤醒后,才能执行
2> 由于多个消费者线程要进入等待队列时,可能产生竞态,为了解决该竞态,同样要引入互斥机制
3> 条件变量表示一个生产者和多个消费者之间的同步关系,而消费者和消费者之间没有同步关系
3> 条件变量的API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 1、创建条件变量 pthread_cond_t cond; 2、初始化条件变量 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 功能:初始化给定的条件变量 参数1:条件变量的地址 参数2:条件变量的属性,一般填NULL 返回值:成功返回0,失败返回一个错误码 3、消费者线程进入等待队列 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 功能:进入等待队列,等待被唤醒 参数1:条件变量的地址 参数2:互斥锁变量地址 返回值:成功返回0,失败返回一个错误码 4、唤醒消费者线程 int pthread_cond_signal(pthread_cond_t *cond); 功能:通知等待队列中的第一个线程进入唤醒状态 参数:条件变量地址 返回值:成功返回0,失败返回一个错误码 int pthread_cond_broadcast(pthread_cond_t *cond); 功能:唤醒等待队列中的所有线程 参数:条件变量地址 返回值:成功返回0,失败返回一个错误码 5、销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond); 功能:销毁条件变量 参数:条件变量地址 返回值:成功返回0,失败返回一个错误码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| #include<myhead.h>
pthread_cond_t cond;
pthread_mutex_t mutex;
void *task1(void*arg) {
sleep(5); printf("%#lx:生产了四辆特斯拉\n", pthread_self()); pthread_cond_broadcast(&cond); pthread_exit(NULL); }
void *task2(void*arg) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); printf("%#lx:消费了一辆特斯拉\n", pthread_self()); pthread_mutex_unlock(&mutex); pthread_exit(NULL); }
int main(int argc, const char *argv[]) { pthread_cond_init(&cond, NULL); pthread_mutex_init(&mutex, NULL); pthread_t tid1,tid2,tid3,tid4,tid5; if(pthread_create(&tid1, NULL, task1, NULL) != 0) { printf("生产者创建失败\n"); return -1; } if(pthread_create(&tid2, NULL, task2, NULL) != 0) { printf("消费者创建失败\n"); return -1; } if(pthread_create(&tid3, NULL, task2, NULL) != 0) { printf("消费者创建失败\n"); return -1; } if(pthread_create(&tid4, NULL, task2, NULL) != 0) { printf("消费者创建失败\n"); return -1; } if(pthread_create(&tid5, NULL, task2, NULL) != 0) { printf("消费者创建失败\n"); return -1; } printf("tid1=%#lx, tid2=%#lx, tid3=%#lx, tid4=%#lx, tid5=%#lx\n",\ tid1,tid2,tid3,tid4,tid5); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_join(tid4, NULL); pthread_join(tid5, NULL); pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); return 0; }
|
1.5 同步机制之读写锁
1> 读写锁是一种特殊的互斥锁,用于解决多个线程同时对共享资源进行读操作的问题
2> 当多个线程同时对共享资源进行读操作时,可以使用读写锁进行同步,提高效率
3> 读写锁的API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| cCopy code1、创建读写锁 pthread_rwlock_t rwlock;
2、初始化读写锁 int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); 功能:初始化读写锁 参数1:读写锁的地址 参数2:读写锁的属性,一般填NULL 返回值:成功返回0,失败返回一个错误码
3、获取读锁 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 功能:获取读锁 参数:读写锁的地址 返回值:成功返回0,失败返回一个错误码
4、获取写锁 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 功能:获取写锁 参数:读写锁的地址 返回值:成功返回0,失败返回一个错误码
5、释放锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 功能:释放锁 参数:读写锁的地址 返回值:成功返回0,失败返回一个错误码
6、销毁读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 功能:销毁读写锁 参数:读写锁的地址 返回值:成功返回0,失败返回一个错误码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <myhead.h>
pthread_rwlock_t rwlock;
void *write_task(void *arg) { while (1) { sleep(2); printf("写线程:%#lx写入数据\n", pthread_self());
pthread_rwlock_unlock(&rwlock); } }
void *read_task(void *arg) { while (1) { pthread_rwlock_rdlock(&rwlock);
printf("读线程:%#lx读取数据\n", pthread_self());
pthread_rwlock_unlock(&rwlock);
sleep(1); } }
int main(int argc, const char *argv[]) {
pthread_rwlock_init(&rwlock, NULL);
pthread_t tid1, tid2, tid3, tid4, tid5; if (pthread_create(&tid1, NULL, write_task, NULL) != 0) { printf("写线程创建失败\n"); return -1; }
for (int i = 0; i < 4; ++i) { if (pthread_create(&tid2, NULL, read_task, NULL) != 0) { printf("读线程创建失败\n"); return -1; } }
printf("tid1=%#lx, tid2=%#lx, tid3=%#lx, tid4=%#lx, tid5=%#lx\n", tid1, tid2, tid3, tid4, tid5);
pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); pthread_join(tid4, NULL); pthread_join(tid5, NULL);
pthread_rwlock_destroy(&rwlock);
return 0; }
|
二、进程间的通信
2.1 通信概念
1> 多个线程之间的信息通信可以使用全局变量来完成,只需注意同步互斥机制即可
2> 多个进程之间的信息通信,不可用使用全局变量完成,因为多个进程之间用户空间是独立的,每个进程拥有自己的全局变量
3> 可以使用外部文件完成多个进程之间通信,一个进程向文件中写入数据,另一个进程从文件中读取数据。但是由于进程的调度是时间片轮询机制,不确定哪个进程先执行,所以该方案需要在多进程同步的基础上完成
4> 由于多个进程之间用户空间是独立的,但是内核空间是共享的,所以可以利用内核空间完成通信,一个进程向内核空间写入数据,另一个进程可以从内核空间取出数据
5> 通信方式
1、内核提供了三种通信方式:无名管道、有名管道、信号
2、System V提供了三种通信方式:消息队列、共享内存、信号量(信号灯集)
3、套接字通信
2.2 管道
1> 在内核空间创建出一个特殊的文件(管道文件)
2> 对于管道文件中的数据操作是一次性的,当管道中的数据被读取后,就不存在了
3> 管道分为无名管道和有名管道:无名管道仅用于亲缘进程间的通信,而有名管道,既可以用于亲缘进程间通信,也可以用于非亲缘进程间通信
4> 管道的通信是半双工通信方式:
Plain Text
自动换行
1 2 3
| 单工:只能A进程向B进程发送数据 半双工:同一时刻,只能A向B发数据或B向A发数据 全双工:同一时刻,AB进程可以互发消息
|
5> 管道遵循先进先出的原则:先写入的数据会先读取出来
6> 一个管道被打开后,会产生两端,分别是读端和写端,当两端全部都被关闭后,管道在内核中就消失了
7> 由于管道存在内核空间,对管道的操作只能使用系统调用(文件IO),而且不能使用lseek移动光标
2.3 无名管道
1> 无名管道:顾名思义就是没有名字的管道,不存在于文件系统中,管道文件创建出来后,在内存中存放
2> 由于在内存中存放,没有文件系统中的名字,所以,一个进程创建出来的无名管道文件,其他进程是没有办法打开的,所以该方式不适用于非亲缘进程间通信
3> 无名管道,只适用于亲缘进程间通信,并且,需要在创建子进程之前将管道文件打开
4> 无名管道的api
1 2 3 4 5 6 7 8
| #include <unistd.h> int pipe(int pipefd[2]); 功能:创建一个无名管道,并通过参数返回管道文件的两端,pipefd[0]表示管道的读端,pipefd[1]表示管道的写端 参数:要返回管道文件两端的文件描述符数组 返回值:成功返回0,失败返回-1并置位错误码
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include<myhead.h> int main(int argc, const char *argv[]) { pid_t pid; int pipefd[2]; if(pipe(pipefd) != 0) { perror("pipe error"); return -1; } printf("pipefd[0]=%d, pipefd[1]=%d\n", pipefd[0], pipefd[1]); pid = fork(); if(pid > 0) { close(pipefd[0]); char buf[128] = "hello world"; write(pipefd[1], buf, sizeof(buf)); close(pipefd[1]); }else if(pid == 0) { close(pipefd[1]); char rbuf[128] = ""; read(pipefd[0], rbuf, sizeof(rbuf)); printf("子进程中收到:%s\n", rbuf); close(pipefd[0]); exit(EXIT_SUCCESS); }else { perror("fork error"); return -1; } wait(NULL); return 0; }
|
练习:创建一个无名管道,父进程循环向管道文件中写入数据,子进程循环从管道文件中读取数据,直到写入"quit"后退出进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include<myhead.h> int main(int argc, const char *argv[]) { pid_t pid; int pipefd[2]; if(pipe(pipefd) != 0) { perror("pipe error"); return -1; } printf("pipefd[0]=%d, pipefd[1]=%d\n", pipefd[0], pipefd[1]); pid = fork(); if(pid > 0) { close(pipefd[0]); char buf[128] = ""; while(1) { memset(buf, 0, sizeof(buf)); read(0, buf, sizeof(buf)); buf[strlen(buf)-1] = '\0'; write(pipefd[1], buf, sizeof(buf)); if(strcmp(buf, "quit") == 0) { break; } } close(pipefd[1]); }else if(pid == 0) { close(pipefd[1]); char rbuf[128] = ""; while(1) { bzero(rbuf, sizeof(rbuf)); read(pipefd[0], rbuf, sizeof(rbuf)); printf("子进程中收到:%s\n", rbuf); if(strcmp(rbuf, "quit") == 0) { break; } } close(pipefd[0]); exit(EXIT_SUCCESS); }else { perror("fork error"); return -1; } wait(NULL); return 0; }
|
5> 管道的特性
1、可以使用无名管道完成自己跟自己通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include<myhead.h> int main(int argc, const char *argv[]) { int pipefd[2]; if(pipe(pipefd) != 0) { perror("pipe error"); return -1; } char wbuf[128] = "hello world"; write(pipefd[1], wbuf, sizeof(wbuf)); char rbuf[128] = ""; read(pipefd[0], rbuf, sizeof(rbuf)); printf("rbu = %s\n", rbuf); close(pipefd[0]); close(pipefd[1]); return 0; }
|
2、管道的大小为:64K
Plain Text
自动换行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include<myhead.h> int main(int argc, const char *argv[]) { //创建一个无名管道 int pipefd[2]; if(pipe(pipefd) != 0) { perror("pipe error"); return -1; } char ch = 'A'; //定义一个字符数据 int count = 0; while(1) { write(pipefd[1], &ch, 1); //将一字节数据写入到管道中 count++; printf("count = %d\n", count); } //关闭文件描述符 close(pipefd[0]); close(pipefd[1]); return 0; }
|
3、管道的读写特点
当读端存在时:写管道有多少写多少,直到写满64k后,在write处阻塞
当读端不存在时:写管道写入数据后,会导致管道破裂
当写端存在时:读管道有多少读多少,没有数据的话,在read处阻塞
当写端不存在时:读管道有多少读多少,没有数据的话,不会在read处阻塞
2.4 有名管道
1> 有名管道:顾名思义就是有名字的管道,存储在文件系统中
2> 有名管道的操作也是一次性的,数据被读取后,就没有了
3> 虽然存储在外部磁盘上,但是,不用于存储信息,只是用于进程间通信
4> 有名管道既可以用于亲缘进程间通信,也可以用于非亲缘进程间的通信
5> 有名管道的api
1 2 3 4 5 6 7 8
| #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 功能:创建一个有名管道文件 参数1:有名管道文件的文件名称 参数2:有名管道的权限,最终权限:mode&~umask 返回值:成功返回0,失败返回-1并置位错误码
|
6> 案例实现
create.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include<myhead.h> int main(int argc, const char *argv[]) { if(mkfifo("./myfifo", 0664) != 0) { perror("mkfifo error"); return -1; } printf("myfifo create success\n"); getchar(); system("rm myfifo"); return 0; }
|
snd.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<myhead.h> int main(int argc, const char *argv[]) { int wfd = -1; if((wfd = open("./myfifo", O_WRONLY)) == -1) { perror("open error"); return -1; } printf("写端打开成功\n"); char wbuf[128] = ""; while(1) { bzero(wbuf, sizeof(wbuf)); printf("请输入>>>>>"); fflush(stdout); read(0, wbuf, sizeof(wbuf)); wbuf[strlen(wbuf)-1] = 0; write(wfd, wbuf, sizeof(wbuf)); if(strcmp(wbuf, "quit") == 0) { break; } } close(wfd); return 0; }
|
recv.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<myhead.h> int main(int argc, const char *argv[]) { int rfd = -1; if((rfd = open("./myfifo", O_RDONLY)) == -1) { perror("open error"); return -1; } printf("读端打开成功\n"); char rbuf[128] = ""; while(1) { bzero(rbuf, sizeof(rbuf)); read(rfd, rbuf, sizeof(rbuf)); printf("收到消息:%s\n", rbuf); if(strcmp(rbuf, "quit") == 0) { break; } } close(rfd); return 0; }
|
2.5 信号
2.5.1 信号基本概念
1> 信号是软件模拟硬件中的中断,中断是硬件实现的,信号是软件实现的
2> 中断:停止当前正在进行的事情,去执行其他事情,执行结束后,继续执行自己的事
3> 一个进程可以给另一个进程发送信号;用户也可以给一个进程发送信号;内核可以向进程发送信号
4> 信号通信,属于异步通信:两个进程各干各的,互不影响
5> 信号处理方式:默认(一般是杀死进程)、忽略、捕获(捕获信号去做另一件事)
2.5.2 信号的种类及功能
可以通过kill -l指令查看所有的信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX 特殊的信号: SIGKILL(9):杀死指定进程信号,该信号,既不能被捕获,也不能被忽略 SIGHUP(1):当进程所在的终端关闭后,终端进程会给当前终端中的每个进程发送该信号,默认操作杀死进程 SIGINT(2):当用户按下ctrl + c后,进程会收到该信号 SIGQUIT(3):退出指定的进程,用户键入ctrl + \时产生 SIGSEGV(11):当指针使用不当或堆栈溢出时,内核空间发射该信号,表示段错误 SIGCHLD(17):当子进程退出后,会向父进程发送该信号,表示我已经死了 SIGCONT(18):让暂停的进程继续运行 SIGSTOP(19)、SIGTSTP(20):让进程暂停执行 注意:SIGKILL和SIGSTOP信号,既不能被捕获,也不能被忽略
|
2.5.3 信号绑定函数(signal)
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 功能:将信号与信号处理方式绑定到一起 参数1:要绑定的信号号 参数2:信号处理方式 SIG_IGN:忽略 SIG_DFL:默认 有参无返回值函数的地址:信号处理函数 返回值:成功返回信号处理函数,失败返回SIG_ERR,并置位错误码
|
1> signal函数的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include<myhead.h>
void handler(int signum) { if(signum == SIGINT) { printf("用户按下了ctrl + c键\n"); } if(signum == SIGTSTP) { printf("用户按下了ctrl + z键\n"); } }
int main(int argc, const char *argv[]) {
if(signal(SIGINT, handler) == SIG_ERR) { perror("signal error"); return -1; } if(signal(SIGTSTP, handler) == SIG_ERR) { perror("signal error"); return -1; } while(1) { sleep(1); printf("我真的还想再活五百年\n"); } return 0; }
|
2> 尝试捕获SIGSTOP信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include<myhead.h>
void handler(int signum) { if(signum == SIGINT) { printf("用户按下了ctrl + c键\n"); } if(signum == SIGTSTP) { printf("用户按下了ctrl + z键\n"); } if(signum == SIGSTOP) { printf("用户按下了ctrl + z键\n"); } }
int main(int argc, const char *argv[]) {
if(signal(SIGSTOP, SIG_DFL) == SIG_ERR) { perror("signal error"); return -1; } while(1) { sleep(1); printf("我真的还想再活五百年\n"); } return 0; }
|
3> 使用信号的方式以非阻塞的形式回收僵尸进程(SIGCHLD)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<myhead.h> //定义信号处理函数 void handler(int signum) { if(signum == SIGCHLD) { //以非阻塞的形式回收僵尸进程,直到所有的僵尸进程全部回收结束 while(waitpid(-1, NULL, WNOHANG) >0 ); } } int main(int argc, const char *argv[]) { //捕获子进程发来的SIGCHLD信号 if(signal(SIGCHLD, handler) == SIG_ERR) { perror("signal error"); return -1; } for(int i=0; i<10; i++) { if(fork() == 0) { exit(EXIT_SUCCESS); } } while(1); return 0; }
|
4> 使用闹钟信号完成一个定时器(SIGALRM),该信号由alarm函数设置的时间超时后产生
1 2 3 4 5 6 7
| #include <unistd.h> unsigned int alarm(unsigned int seconds); 功能:启动一个定时器,当定时器超时后,会发送一个SIGALRM信号 参数:超时时间,以秒为单位 返回值:如果之前没有设置定时器,则返回0,如果已经设置了,则返回上一个定时器的剩余时间
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include<myhead.h> int main(int argc, const char *argv[]) { printf("%d\n", alarm(10)); sleep(5); printf("%d\n", alarm(10)); while(1); return 0; }
|
使用SIGALRM信号模拟斗地主出牌场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<myhead.h>
void handler(int signo) { if(signo == SIGALRM) { printf("系统已经随机为您出一张牌\n"); alarm(5); } } int main(int argc, const char *argv[]) { char ch; if(signal(SIGALRM, handler) == SIG_ERR) { perror("signal error"); return -1; } while(1) { alarm(5); printf("请出牌:"); scanf("%c", &ch); getchar(); printf("您出的牌为:%c\n", ch); } return 0; }
|
2.5.4 信号发送函数(kill、raise)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <signal.h> int kill(pid_t pid, int sig); 功能:向指定的进程或进程组内发射信号 参数1:信号号 >0: 表示向特定的进程发送信号 =0:向当前进程所在的进程组内发射信号 =-1:向所有进程发送信号 <-1:向指定进程组(组ID为pid的绝对值)发送信号 参数2:要发送的信号号 返回值:成功返回0,失败返回-1并置位错误码 #include <signal.h> int raise(int sig); 功能:向当前进程发送信号:kill(getpid(), sig) 参数:要发送的信号号 返回值:成功返回0,失败返回非0数字
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include<myhead.h>
void handler(int signo) { if(signo == SIGUSR1) { printf("逆子何至于此\n"); raise(SIGKILL); } } int main(int argc, const char *argv[]) { pid_t pid = fork(); if(pid > 0) { if(signal(SIGUSR1, handler) == SIG_ERR) { perror("signal error"); return -1; } while(1) { printf("我真的还想再活五百年\n"); sleep(1); } }else if(pid == 0) { printf("人生得意须尽欢,莫使金樽空对月\n"); sleep(3); printf("我看透了红尘, 父亲跟我一起走吧\n"); kill(getppid(), SIGUSR1); exit(EXIT_SUCCESS); }else { perror("fork error"); return -1; } return 0; }
|
2.6 System V进程间通信(IPC对象)
1> 种类:
消息队列:在内核空间维护了一个队列,所有进程都可以向队列中存放数据,也可以从队列中取出数据
共享内存:将物理内存映射出来,共所有进程使用
信号量(信号灯集):主要完成进程的同步工作
2> 关于IPC的相关指令
ipcs: 查看所有IPC对象的详细信息
ipcs -q:只查看消息队列的详细信息
ipcs -m:只查看共享内存的详细信息
ipcs -s:只查看信号量的详细信息
ipcrm -q/-m/-s ID:表示删除指定的消息队列、共享内存、信号量
3> 关于系统5提供的进程间通信对象,都需要使用一个key值
2.7 消息队列
1> 原理图
2> 有关消息队列的API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| 1、创建key值 #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id); 功能:创建IPC通信的秘钥,用于创建消息队列、共享内存、信号灯集使用 参数1:文件路径(必须是已经存在的路径),可以是任意一个 参数2:是一个随机值 返回值:一个四字节的整数,由两个参数组成,其中前8位由给定的文件的设备号提供, 中间16位由文件的inode号提供, 最后8位由第二个参数的底8位提供 失败返回-1并置位错误码 2、创建消息队列 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg); 功能:通过给定的key值在内核空间创建一个消息队列的通信对象 参数1:key值,可以由ftok函数产生,也可以是IPC_PRIVATE,如果是IPC_PRIVATE,表示该通信方式只适用于亲缘进程间 参数2:消息队列的模式位 IPC_CREAT:表示创建一个消息队列,如果消息队列存在,则直接打开消息队列,如果不存在则创建消息队列 IPC_EXCL:表示如果消息队列存在,则创建报错 权限:0664 例如:mesget(key, IPC_CREAT|0664) 返回值:成功返回创建出来的消息队列的id号,失败返回-1并置位错误码 3、向消息队列中存放数据 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 功能:向指定下的消息队列中存放消息 参数1:消息队列的id号 参数2:消息的起始地址 struct msgbuf { long mtype; 消息类型,用于后期其他进程从消息队列中选择消息进行取出 char mtext[1]; 消息数据 }; 参数3:要存放消息正文的大小,不包含消息类型 参数4:是否阻塞 IPC_NOWAIT:非阻塞 0:表示阻塞 返回值:成功返回0,失败返回-1并置位错误码 4、从消息队列中取消息 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 功能:从给定的消息队列中取出一条消息 参数1:消息队列id 参数2:要存放消息的容器 参数3:要取出消息的正文大小 参数4:要取出的消息类型 =0:表示取出消息队列中的第一个数据 >0:表示读取消息队列中类型为msgtyp的第一条消息 <0:表示从消息队列中取出一个消息,消息类型是小于或等于给定的msgtyp的绝对值中的第一个 5 --> 10 --> 4 --> 100 --> 20 type:-10 取出消息为:5 参数5:是否阻塞 IPC_NOWAIT:非阻塞 0:表示阻塞 返回值:成功返回实际读取的字节的个数,失败返回-1并置位错误码 5、消息队列的控制函数 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf); 功能:消息队列的控制函数 参数1:要操作的消息队列的id号 参数2:要执行的操作 IPC_RMID:表示删除指定的消息队列(常用),此时,第三个参数可以忽略填NULL IPC_STAT:将消息队列的信息获取下来,并放入buf指向的一个结构体变量中 IPC_SET:表示设置消息队列的属性,要设置的信息在buf中 struct msqid_ds结构体类型: struct msqid_ds { struct ipc_perm msg_perm; 当前消息队列的所属用户和权限 time_t msg_stime; 最新一次存放消息的时间 time_t msg_rtime; 最新一次取消息的时间 time_t msg_ctime; 最新一次状态更改的时间 unsigned long __msg_cbytes;
msgqnum_t msg_qnum;
msglen_t msg_qbytes;
pid_t msg_lspid; 最新存放消息的进程进程号 pid_t msg_lrpid; 最新取消息的进程的进程号 }; 关于第一个成员的结构体: struct ipc_perm { key_t __key; 消息队列的key值 uid_t uid; 当前用户操作的用户id gid_t gid; 当前操作的组id uid_t cuid; 创建消息队列进程的用户id gid_t cgid; 创建消息队列进程的组id unsigned short mode; 权限 unsigned short __seq; 队列号 }; 返回值:成功返回0,失败返回-1并置位错误码
|
发送端实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include<myhead.h>
struct msgbuf { long mtype; char mtext[1024]; };
#define SIZE (sizeof(struct msgbuf) - sizeof(long)) int main(int argc, const char *argv[]) { key_t key = 0; if((key = ftok("/", 't')) == -1) { perror("fork error"); return -1; } printf("key = %#x\n", key); int msqid = 0; if((msqid=msgget(key, IPC_CREAT|0664)) == -1) { perror("msgget error"); return -1; } printf("msqid = %d\n", msqid); struct msgbuf buf = {.mtype=100}; while(1) { printf("请输入要存放的数据:"); scanf("%s", buf.mtext); getchar(); msgsnd(msqid, &buf, SIZE, 0); printf("发送成功\n"); if(strcmp(buf.mtext, "quit") == 0) { break; } } return 0; }
|
接收端实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include<myhead.h>
struct msgbuf { long mtype; char mtext[1024]; };
#define SIZE (sizeof(struct msgbuf) - sizeof(long)) int main(int argc, const char *argv[]) { key_t key = 0; if((key = ftok("/", 't')) == -1) { perror("fork error"); return -1; } printf("key = %#x\n", key); int msqid = 0; if((msqid=msgget(key, IPC_CREAT|0664)) == -1) { perror("msgget error"); return -1; } printf("msqid = %d\n", msqid); struct msgbuf buf; while(1) { msgrcv(msqid, &buf, SIZE, 0, 0); printf("收到消息为:%s\n", buf.mtext); if(strcmp(buf.mtext, "quit") == 0) { break; } } if(msgctl(msqid, IPC_RMID, NULL) == -1) { perror("msgctl error"); return -1; } return 0; }
|
2.8 共享内存
1> 所谓共享内存,是将物理内存映射到不同的进程中,不同进程直接对映射出来的共享内存进行操作,无需进行用户空间和内核空间的切换
2> 原理图
3> 共享内存的相关API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| 1、创建key值 #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id); 功能:创建IPC通信的秘钥,用于创建消息队列、共享内存、信号灯集使用 参数1:文件路径(必须是已经存在的路径),可以是任意一个 参数2:是一个随机值 返回值:一个四字节的整数,由两个参数组成,其中前8位由给定的文件的设备号提供, 中间16位由文件的inode号提供, 最后8位由第二个参数的底8位提供 失败返回-1并置位错误码 2、申请物理内存,创建出共享内存段 #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); 功能:申请size空间的物理内存,映射出共享内存段,size是4096的倍数 参数1:创建共享内存的key值 参数2:要申请的大小,4096的倍数 参数3:消息队列的模式位 IPC_CREAT:表示创建一个共享内存,如果共享内存存在,则直接打开共享内存,如果不存在则创建共享内存 IPC_EXCL:表示如果共享内存存在,则创建报错 权限:0664 例如:shmget(key, IPC_CREAT|0664) 返回值:成功返回共享内存的id,失败返回-1并置位错误码 3、将共享内存映射到用户空间 #include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); 功能:将共享内存地址映射到用户空间 参数1:共享内存id 参数2:共享内存的起始页地址,一般填NULL,让系统自动选择合适的对齐页地址 参数3:操作标识位 SHM_RDONLY:表示对共享内存只读功能 0:表示既可以读也可以写 返回值:成功返回映射的地址,失败返回(void *)-1,并置位错误码 4、取消共享内存的映射 int shmdt(const void *shmaddr); 功能:取消共享内存段的用户空间地址的映射 参数:用户空间映射的地址 返回值:成功返回0,失败返回-1并置位错误码 5、共享内存的控制函数 #include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); 功能:根据给定的cmd的值确定执行方案 参数1:共享内存id 参数2:cmd控制 IPC_STAT:获取状态 IPC_SET:设置状态 IPC_RMID:删除共享内存 参数3:要设置的结构体起始地址 返回值:成功返回0,失败返回-1并置位错误码
|
1> 发送端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include<myhead.h> #define PAGE_SIZE 4096 int main(int argc, const char *argv[]) { key_t key = -1; if((key = ftok("/", 't')) == -1) { perror("ftok error"); return -1; } printf("key = %#x\n", key); int shmid = 0; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT|0664)) == -1) { perror("shmget error"); return -1; } printf("shmid = %d\n", shmid); char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void *)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { fgets(addr, PAGE_SIZE, stdin); addr[strlen(addr) - 1] = '\0'; if(strcmp(addr, "quit") == 0) { break; } } while(1); return 0; }
|
2> 接收端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include<myhead.h> #define PAGE_SIZE 4096 int main(int argc, const char *argv[]) { key_t key = -1; if((key = ftok("/", 't')) == -1) { perror("ftok error"); return -1; } printf("key = %#x\n", key); int shmid = 0; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT|0664)) == -1) { perror("shmget error"); return -1; } printf("shmid = %d\n", shmid); char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void *)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { printf("共享内存中的数据为:%s\n", addr); sleep(1); if(strcmp(addr, "quit") == 0) { break; } } if(shmdt(addr) == -1) { perror("shmdt error"); return -1; } if(shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl error"); return -1; } return 0; }
|
2.9 信号量(信号灯集)
1> 信号灯集主要完成进程间同步工作,将多个信号灯,放在一个信号灯集中,每个信号灯控制一个进程
2> 每个灯维护了一个value值,当value值等于0时,申请该资源的进程处于阻塞状态,直到其他进程将该灯中维护的value值增加到大于0
3> 原理图
4> 有关信号灯集的API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| 1、创建key值 #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id); 功能:创建IPC通信的秘钥,用于创建消息队列、共享内存、信号灯集使用 参数1:文件路径(必须是已经存在的路径),可以是任意一个 参数2:是一个随机值 返回值:一个四字节的整数,由两个参数组成,其中前8位由给定的文件的设备号提供, 中间16位由文件的inode号提供, 最后8位由第二个参数的底8位提供 失败返回-1并置位错误码 2、创建信号灯集 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); 功能:创建一个信号灯集 参数1:key值,可以通过ftok创建出来,也可以使用IPC_PRIVATE 参数2:信号灯集中信号灯的个数,每一个信号灯控制一个进程 参数3:信号灯集的模式位 IPC_CREAT:表示创建一个信号灯集,如果信号灯集存在,则直接打开信号灯集,如果不存在则创建信号灯集 IPC_EXCL:表示如果信号灯集存在,则创建报错 权限:0664 例如:shmget(key, IPC_CREAT|0664) 返回值:成功返回信号灯集的ID,失败返回-1并置位错误码 3、信号灯集控制函数 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...); 功能:信号灯集的控制操作 参数1:信号灯集的ID 参数2:表示要操作的信号灯的编号,从0开始 参数3:操作命令 IPC_RMID:删除信号灯集,如果参数为IPC_RMID时,参数2和参数4可以忽略 IPC_STAT:获取信号灯集的属性 IPC_SET:设置信号灯集的属性 SETVAL:表示要设置信号灯的值 GETVAL:表示要获取信号灯的值 SETALL:表示对所有信号灯进行设置 GETALL:表示获取所有信号灯的值 参数4:一个可变参数,取决于cmd,如果需要改参数时,应该提供一个共用体,定义如下 union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf;
}; 说明: 1)如果cmd为SETVAL,则共用体只需要使用整形变量即可 2)如果cmd为GETALL, SETALL,则需要提供一个一维数组,表示给全部的信号灯进行值的操作 3)如果cmd为IPC_STAT, IPC_SET ,则需要定义一个结构体变量,将地址赋值到当前共用体成员中 struct semid_ds { struct ipc_perm sem_perm; time_t sem_otime; time_t sem_ctime; unsigned long sem_nsems; }; struct ipc_perm { key_t __key; uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; unsigned short mode; unsigned short __seq; }; 返回值:成功返回0,失败返回-1并置位错误码 举个例子:将信号灯集中的0号灯的值设置为1 union semun su; su.val = 1; semctl(semid, 0, SETVAL, su); 再举个例子:删除信号灯集 semctl(semid, 0, IPC_RMID); 4、申请和释放信号灯的资源函数 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, size_t nsops); 功能:完成对指定信号灯的申请和释放资源 参数1:信号灯集id 参数2:要执行的操作等的指针 unsigned short sem_num; 信号灯编号 short sem_op; 申请还是释放操作,-1表示申请资源, 1表示释放资源 short sem_flg; 阻塞还是非阻塞,0表示阻塞,IPC_NOWAIT表示非阻塞 参数3:操作的灯的个数 返回值:成功返回0,失败返回-1并置位错误码 举个例子:对0号灯进行申请资源操作 struct sembuf buf; buf.sem_num = 0; buf.sem_op = -1; buf.sem_flg = 0; semop(semid, &buf, 1);
|
1> 将信号灯集的相关函数二次封装
sem.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #ifndef __SEM_H__ #define __SEM_H__
int create_sem(int semcount);
int P(int semid, int semno);
int V(int semid, int semno);
int delete_sem(int semid); #endif
|
sem.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| #include <myhead.h> union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf;
};
int set_semno_value(int semid, int semno) { int val; printf("请输入第%d号灯的值:", semno); scanf("%d", &val); union semun su; su.val = val; if(semctl(semid, semno, SETVAL, su)==-1) { perror("semctl error"); return -1; } return 0; }
int create_sem(int semcount) { key_t key = 0; if((key = ftok("/", 'k')) == -1) { perror("ftok error"); return -1; } int semid = 0; if((semid = semget(key, semcount, IPC_CREAT|IPC_EXCL|0664))==-1) { if(errno == EEXIST) { semid = semget(key, semcount, IPC_CREAT|0664); return semid; } perror("semget error"); return -1; } for(int i=0; i<semcount; i++) { set_semno_value(semid, i); } return semid; }
int P(int semid, int semno) { struct sembuf buf; buf.sem_num = semno; buf.sem_op = -1; buf.sem_flg = 0; if(semop(semid, &buf, 1) == -1) { perror("semop error"); return -1; } return 0; }
int V(int semid, int semno) { struct sembuf buf; buf.sem_num = semno; buf.sem_op = 1; buf.sem_flg = 0; if(semop(semid, &buf, 1) == -1) { perror("semop error"); return -1; } return 0; }
int delete_sem(int semid) { if(semctl(semid, 0, IPC_RMID) == -1) { perror("semctl error"); return -1; } return 0; }
|
shmsnd.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include<myhead.h> #define PAGE_SIZE 4096 #include"sem.h" int main(int argc, const char *argv[]) { int semid = create_sem(2); key_t key = -1; if((key = ftok("/", 't')) == -1) { perror("ftok error"); return -1; } printf("key = %#x\n", key); int shmid = 0; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT|0664)) == -1) { perror("shmget error"); return -1; } printf("shmid = %d\n", shmid); char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void *)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { P(semid, 0); fgets(addr, PAGE_SIZE, stdin); addr[strlen(addr) - 1] = '\0'; V(semid, 1); if(strcmp(addr, "quit") == 0) { break; } } while(1); return 0; }
|
shmrcv.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include<myhead.h> #define PAGE_SIZE 4096 #include"sem.h" int main(int argc, const char *argv[]) { int semid = create_sem(2); key_t key = -1; if((key = ftok("/", 't')) == -1) { perror("ftok error"); return -1; } printf("key = %#x\n", key); int shmid = 0; if((shmid = shmget(key, PAGE_SIZE, IPC_CREAT|0664)) == -1) { perror("shmget error"); return -1; } printf("shmid = %d\n", shmid); char *addr = (char *)shmat(shmid, NULL, 0); if(addr == (void *)-1) { perror("shmat error"); return -1; } printf("addr = %p\n", addr); while(1) { P(semid, 1); printf("共享内存中的数据为:%s\n", addr); V(semid, 0); if(strcmp(addr, "quit") == 0) { break; } } if(shmdt(addr) == -1) { perror("shmdt error"); return -1; } if(shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl error"); return -1; } delete_sem(semid); return 0; }
|