1.信号
信号(signal)是软件中断,是历程之间相互传递消息的一种方法,用于关照历程发生了变乱,但
是,不能给历程传递任何数据。
信号产生的缘故原由有很多,在 Shell 中,可以用 kill 和 killall 命令发送信号:
- ki -信号的类型 进程编号
- killall -信号的类型 进程名
复制代码 如果命令无效,需安装psmisc
信号的处置惩罚:
- 对该信号的处置惩罚采取系统的默认操作,大部门的信号的默认操作是终止历程
- 设置信号的处置惩罚函数,收到信号后,由该函数来处置惩罚
- 忽略某个信号,对该信号不做任何处置惩罚,就像未发生过一样
signal()函数可以设置步调对信号的处置惩罚方式
- 函数声明:
- sighandler_t signal(int signum,sighandler_t handler);
-
- signum //信号编号
- handler //信号的处理方式,有三种
- 1)SIG_DFL:恢复参数signum所指信号处理方法
- 2)一个自定义处理信号的函数,函数的形参是信号的编号
- 3)SIG_IGN:忽略参数signum所指信号
复制代码- #include<iostream>
- #include<signal.h>
- #include<unistd.h>
- using namespace std;
- void func(int signum)
- {
- cout<<"signals"<<endl;
- }
- int main()
- {
- signal(1,func);//注册回调函数func()
- signal(15,func);
- signal(2,SIG_IGN);//忽略2信号
- signal(15,SIG_DFL);//恢复15信号
-
- alarm(5); //5s定时发送14信号
-
- while(true)
- {
- cout<<1<<endl;
- sleep(1);
- }
- }
复制代码 应用场合:
服务步调运行在后台,如果想让中止它,杀掉不是个好办法,因为历程被杀的时间,是突然殒命没有安排善后工作。
如果向服务步调发送一个信号,服务步调收到这个信号后,调用一个函数,在函数中编写善后的代码,步调就可以有筹划的退出。
向服务步调发送0的信号,可以检测步调是否存活。
- [root@localhost 06demoerror]# killall -0 demo_error
- demo_error: no process found
复制代码- #include<iostream>
- #include<signal.h>
- #include<unistd.h>
- using namespace std;
- void EXIT(int signum)
- {
- cout<<"程序退出\n"<<endl;
- exit(0);
- }
- int main(int argc,cgar *argv[])
- {
- //忽略所有信号,防止进程被信号异常终止
- for(int ii = 1;ii <= 64;ii++) signal(ii,SIG_IGN);
-
- //收到2和15信号(Ctrl+C和kill、killall),程序退出
- signal(2,EXIT);
- signal(15,EXIT);
-
- while(true)
- {
- cout<<"执行一次任务"<<endl;
- sleep(1);
- }
- }
复制代码 2.历程终止
有八种方式可以终止历程,此中5中为正常终止
1)在main()函数中用return返回
2)在恣意函数中调用exit()
3)在恣意函数中调用_exit()或 _Exit()函数
4)在最后一个线程从其他启动例程(线程主函数)用return返回
5)在最后一个线程中调用pthread_exit()返回
非常终止有三种方式
6)调用abort()函数终止
7)吸取到一个信号
8)最后一给线程对取消请求作出响应
在main()函数中,return返回值即终止状态,如果没有return或调用exit(),历程终止状态为0
在Shell中检察简称终止状态
- [root@localhost 06demoerror]# ./demo
- [root@localhost 06demoerror]# echo $?
- 0
复制代码 正常终止历程的三个函数
- void exit(int status);
- void _exit(int status);
- void _Exit(int status); //status为进程终止状态
复制代码 如果历程被非常终止,终止状态为 非0
用于服务步调的调度、日志和监控
资源释放的标题:
- return 体现函数返回,会调用局部对象的析构函数,main()函数中的 return 还会调用全局对象的析构函数。
- exit()体现终止历程,不会调用局部对象的析构函数,只调用全局对象的析构函数。
- exit()会实验清理工作,然后退出,exit()和 Exit()直接退出,不会实验清理工作。
历程的终止函数:atexit()登记终止函数(最多32个),这些函数exit()自动调用
- int atexit(void (*function)(void));
复制代码 exit()调用终止函数的次序与登记时相反
3.调用可实验步调
Linux提供了system()函数和exec函数族,在C++步调中,可以实验其他步调(二进制文件、操作系统命令或者Shell脚本)
system()函数
- #include<stdlib.h>
- int system(const char * string);
- 执行程序不存在,返回非0
- 执行成功,程序终止状态是0,system()函数返回0
- 执行成功,程序终止状态不是0,system()函数返回非0
复制代码 exec()函数族
exec函数族提供了另一种在历程中调用步调(二进制文件或Shell脚本)的方法
- 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[]);
复制代码 注意:
1)如果实验失败则直接返回-1,失败缘故原由存于errno中
2)新历程的历程编号和原历程雷同,但是新历程代替了原历程的代码段、数据段和堆栈
3)如果实验乐成则函数不会返回,当在主步调乐成调用了exec后,被调用的步调将代替调用者步调,也就是说exec函数之后的代码都不会被实验
4)在实际开发中,最常用execl()和execv()
4.历程创建
整个Linux系统全部的历程是一个树形结构
- 0号历程是所有历程的祖先,他创建了1号和2号历程
- 1号历程(systemed)负责实验内核的初始化工作和举行系统设置
- 2号历程(kthreadd)负责所有内核线程的调度和管理
- yum -y install psmisc
- pstree -p 进程编号 //查看进程树
复制代码 每个历程都有一个非负整数体现的唯一历程ID。固然是唯一的,但是历程ID可以复用。当一个历程终止后,其历程ID就成了复用的候选者。Linux采取耽误服用算法,让新建历程的ID不同于迩来终止的历程所使用的ID。这样防止了新历程被误认为是使用了同一ID的某个已终止历程。
- pid_t getpid(void) //获取当前进程ID
- pid_t getppid(void) //获取父进程ID
复制代码 fork()函数
一个现有的历程可以调用fork()函数创建一个新历程
fork()函数被调用一次,但返回两次。两次返回的区别是子历程返回值是0,而父历程返回值是子历程的历程ID。
子历程和父历程继承实验fork()之后的代码,子历程是父历程的副本。
子历程得到了父历程数据空间、堆和栈的副本。
子历程拥有的是副本,不是共享
fork()之后,父历程和子历程的实验次序是不确定的。
fork()两种用法:
- 父历程复制自己,然后,父历程和子历程分别实验不同的代码。这种用法在网络服务步调中很常见,父历程期待客户端的毗连请求,当请求到达时,父历程调用fork(),让子历程处置惩罚些请求,而父历程则继承期待下一个毗连请求。
- 历程要实验另一个步调。这种用法在 Shell 中很常见,子历程从 fork()返回后立即调用 exec。
共享文件:fork()的一个特性是在父历程中打开的文件形貌符都会被复制到子历程中,父历程和子历程共享同一个文件偏移量。
如果父历程和子历程写同一文件形貌符指向的文件,但又没有任何同步形式,那么他们的输出可能会相互混合。
vfork()函数
vfork()函数的调用和返回值与 fork()雷同,但两者的语义不同。
vfork()函数用于创建一个新历程,而该新历程的目标是 exec 一个新步调,它不复制父历程的所在空间,因为子历程会立即调用 exec,于是也就不会使用父历程的所在空间。如果子历程使用了父历程的所在空间,可能会带来未知的结果。
vfork()和fork()的另一个区别是:vfork()包管子历程先运行,在子历程调用exec或exit()之后,父历程才规复运行
5.僵尸历程
如果父历程比子历程先退出,子历程将被1号历程托管(这也是一种让步调在后台运行的方法)。
如果子历程比父历程先退出,而父历程没有处置惩罚子历程退出的信息,那么,子历程将成为僵尸进
程。
僵尸历程有什么危害?
内核为每个子历程保留了一个数据结构,包罗历程编号、终止状态、使用CPU 时间等。父历程如果处置惩罚了子历程退出的信息,内核就会释放这个数据结构,父历程如果没有处置惩罚子历程退出的信息,内核就不会释放这个数据结构,子历程的历程编号将一直被占用。系统可用的历程编号是有限的,如果产生了大量的僵尸历程,将因为没有可用的历程编号而导致系统不能产生新的历程。
僵尸历程的制止:
1)子历程退出的时间,内核会向父历程发头 SIGCHLD 信号,如果父历程用 signal(SIGCHLD,SIGGN)关照内核,体现自己对子历程的退出不感兴趣,那么子历程退出后会立即释放数据结构。
2)父历程通过 wait()/waitpid()等函数期待子历程结束,在子历程退出之前,父历程将阻塞期待。
- pid_t wait(int *stat_loc);
- pid_t waitpid(pid_t pid, int *stat_loc, int options);
- pid_t wait3(int *status, int options, struct rusage *rusage);
- pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
复制代码 返回值是子历程的编号
stat_loc 是子历程终止的信息:
a)如果是正常终止,宏 WIFEXITED(stat loc)返回真,宏 WEXITSTATUS(stat_loc)可获取终止状态;
b)如果是非常终止,宏 WTERMSIG(stat loc)可获取终止历程的信号
3)如果父历程很忙,可以捕捉 SIGCHLD 信号,在信号处置惩罚函数中调用 wait()/waitpid()。
6.多历程与信号
Linux 操作系统提供了 kill和 killall 命令向历程发送信号,在步调中,可以用kill()函数向别的历程发送信号。
函数声明:
- int kill(pid t pid, int sig);
复制代码 kil()函数将参数 sig 指定的信号给参数 pid 指定的历程。
参数 pid 有几种情况:
1)pid>0 将信号传给历程号为 pid 的历程。
2)pid=0 将信号传给和如今历程雷同历程组的所有历程,常用于历程给子历程发送信号,注意,发送信号者历程也会收到自己发出的信号。
3)pid=-1 将信号广播传送给系统内所有的历程,比方系统关机时,会向所有的登录窗口广播关机信息。
sig:准备发送的信号代码,如果其值为0则没有任何信号送出,但是系统会实验错误查抄,通常会利用 sig 值为零来检验某个历程是否仍在运行。
返回值说明: 乐成实验时,返回0;失败返回-1,errno 被设置。
8.共享内存
多线程共享历程的所在空间,如果多个线程必要访问同一块内存,用全局变量就可以了。
在多历程中,每个历程的所在空间是独立的,不共享的,如果多个历程必要访问同一块内存,不能用全局变量,只能用共享内存。“
共享内存(Shared Memory)答应多个历程(不要求历程之间有血缘关系)访问同一个内存空间是多个历程之间共享和传递数据最高效的方式。历程可以将共享内存毗连到它们自己的所在空间中,如果某个历程修改了共享内存中的数据,别的的历程读到的数据也将会改变。
共享内存并未提供锁机制,也就是说,在某一个历程对共享内存的举行读/写的时间,不会制止别的历程对它的读/写。如果要对共享内存的读/写加锁,可以使用信号量。
Linux 中提供了一组函数用于操作共享内存。
shmget函数
该函数用于创建/获取共享内存。
- int shmget(key_t key, size_t size, int shmflg);
复制代码 key 共享内存的键值,是一个整数(typedef unsigned int key_t),一样平常采取十六进制,比方0x5005,不同共享内存的 key 不能雷同。
size 待创建的共享内存的巨细,以字节为单位。
shmfg 共享内存的访问权限,与文件的权限一样,比方 0666|IPC_CREAT,体现全部用户对它可读写,IPC_CREAT体现如果共享内存不存在,就创建它。
返回值:乐成返回共享内存的id(一个大于0的整数),失败返回-1
- ipcs -m //查看系统的共享内存
- ipcrm -m 共享内存id //手工删除共享内存
复制代码 shmat 函数
该函数用于把共享内存毗连到当前历程的所在空间。
- void *shmat(int shmid, const void *shmaddr, int shmflg);
复制代码 shmid 由 shmget()函数返回的共享内存标识。
shmaddr 指定共享内存毗连到当前历程中的所在位置,通常填0,体现让系统来选择共享内存的
所在。
shmflg 标记位,通常填 0。
调用乐成时返回共享内存起始所在,失败返回(void*)-1
shmdt 函数
该函数用于将共享内存从当前历程中分离,相称于shmat()函数的反操作。
- int shmdt(const void *shmaddr);
复制代码 shmaddr shmat()函数返回的所在。
调用乐成时返回 0,失败时返回-1。
shmctl 函数
该函数用于操作共享内存,最常用的操作是删除共享内存。
int shmctl(int shmid, int command, struct shmid ds *buf);
shmid shmget()函数返回的共享内存 id。
command操作共享内存的指令,如果要删除共享内存,填IPC RMID。
buf 操作共享内存的数据结构的所在,如果要删除共享内存,填 0。
调用乐成时返回0,失败时返回-1。
注意,用root 创建的共享内存,不管创建的权限是什么,普通用户无法删除。
9.循环队列
- 共享内存不能自动扩展,只能采取C++内置的数据类型
- 共享内存不能采取STL容器,也不能使用移动语义
- 如果要实现多历程的生产/消费者模型,只能采取循环队列
- //_public.cpp
- #include "_public.h"
- // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
- // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
- // 如果用于生产消费者模型,value填0,sem_flg填0。
- bool csemp::init(key_t key,unsigned short value,short sem_flg)
- {
- if (m_semid!=-1) return false; // 如果已经初始化了,不必再次初始化。
- m_sem_flg=sem_flg;
- // 信号量的初始化不能直接用semget(key,1,0666|IPC_CREAT)
- // 因为信号量创建后,初始值是0,如果用于互斥锁,需要把它的初始值设置为1,
- // 而获取信号量则不需要设置初始值,所以,创建信号量和获取信号量的流程不同。
- // 信号量的初始化分三个步骤:
- // 1)获取信号量,如果成功,函数返回。
- // 2)如果失败,则创建信号量。
- // 3) 设置信号量的初始值。
- // 获取信号量。
- if ( (m_semid=semget(key,1,0666)) == -1)
- {
- // 如果信号量不存在,创建它。
- if (errno==ENOENT)
- {
- // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。
- if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1)
- {
- if (errno==EEXIST) // 如果错误代码是信号量已存在,则再次获取信号量。
- {
- if ( (m_semid=semget(key,1,0666)) == -1)
- {
- perror("init 1 semget()"); return false;
- }
- return true;
- }
- else // 如果是其它错误,返回失败。
- {
- perror("init 2 semget()"); return false;
- }
- }
- // 信号量创建成功后,还需要把它初始化成value。
- union semun sem_union;
- sem_union.val = value; // 设置信号量的初始值。
- if (semctl(m_semid,0,SETVAL,sem_union) < 0)
- {
- perror("init semctl()"); return false;
- }
- }
- else
- { perror("init 3 semget()"); return false; }
- }
- return true;
- }
- // 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
- bool csemp::wait(short value)
- {
- if (m_semid==-1) return false;
- struct sembuf sem_b;
- sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。
- sem_b.sem_op = value; // P操作的value必须小于0。
- sem_b.sem_flg = m_sem_flg;
- if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }
- return true;
- }
- // 信号量的V操作(把信号量的值减value)。
- bool csemp::post(short value)
- {
- if (m_semid==-1) return false;
- struct sembuf sem_b;
- sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。
- sem_b.sem_op = value; // V操作的value必须大于0。
- sem_b.sem_flg = m_sem_flg;
- if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; }
- return true;
- }
- // 获取信号量的值,成功返回信号量的值,失败返回-1。
- int csemp::getvalue()
- {
- return semctl(m_semid,0,GETVAL);
- }
- // 销毁信号量。
- bool csemp::destroy()
- {
- if (m_semid==-1) return false;
- if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }
- return true;
- }
- csemp::~csemp()
- {
- }
复制代码 测试步调1
- // demo1.cpp,本程序演示循环队列的使用。
- #include "_public.h"
- int main()
- {
- using ElemType=int;
- squeue<ElemType,5> QQ;
- ElemType ee; // 创建一个数据元素。
- cout << "元素(1、2、3)入队。\n";
- ee=1; QQ.push(ee);
- ee=2; QQ.push(ee);
- ee=3; QQ.push(ee);
- cout << "队列的长度是" << QQ.size() << endl;
- QQ.printqueue();
- ee=QQ.front(); QQ.pop(); cout << "出队的元素值为" << ee << endl;
- ee=QQ.front(); QQ.pop(); cout << "出队的元素值为" << ee << endl;
- cout << "队列的长度是" << QQ.size() << endl;
- QQ.printqueue();
- cout << "元素(11、12、13、14、15)入队。\n";
- ee=11; QQ.push(ee);
- ee=12; QQ.push(ee);
- ee=13; QQ.push(ee);
- ee=14; QQ.push(ee);
- ee=15; QQ.push(ee);
- cout << "队列的长度是" << QQ.size() << endl;
- QQ.printqueue();
- }
复制代码 输出
- [root@localhost 07demosqueue]# g++ -o demo1 demo1.cpp _public.h _public.cpp
- [root@localhost 07demosqueue]# ./demo1
- 元素(1、2、3)入队。
- 队列的长度是3
- m_data[0],value=1
- m_data[1],value=2
- m_data[2],value=3
- 出队的元素值为1
- 出队的元素值为2
- 队列的长度是1
- m_data[2],value=3
- 元素(11、12、13、14、15)入队。
- 循环队列已满,入队失败。
- 队列的长度是5
- m_data[2],value=3
- m_data[3],value=11
- m_data[4],value=12
- m_data[0],value=13
- m_data[1],value=14
复制代码 测试步调2
- // demo2.cpp,本程序演示基于共享内存的循环队列。
- #include "_public.h"
- int main()
- {
- using ElemType=int;
- // 初始化共享内存。
- int shmid=shmget(0x5005, sizeof(squeue<ElemType,5>), 0640|IPC_CREAT);
- if ( shmid ==-1 )
- {
- cout << "shmget(0x5005) failed.\n"; return -1;
- }
- // 把共享内存连接到当前进程的地址空间。
- squeue<ElemType,5> *QQ=(squeue<ElemType,5> *)shmat(shmid,0,0);
- if ( QQ==(void *)-1 )
- {
- cout << "shmat() failed\n"; return -1;
- }
- QQ->init(); // 初始化循环队列。
- ElemType ee; // 创建一个数据元素。
- cout << "元素(1、2、3)入队。\n";
- ee=1; QQ->push(ee);
- ee=2; QQ->push(ee);
- ee=3; QQ->push(ee);
- cout << "队列的长度是" << QQ->size() << endl;
- QQ->printqueue();
- ee=QQ->front(); QQ->pop(); cout << "出队的元素值为" << ee << endl;
- ee=QQ->front(); QQ->pop(); cout << "出队的元素值为" << ee << endl;
- cout << "队列的长度是" << QQ->size() << endl;
- QQ->printqueue();
- cout << "元素(11、12、13、14、15)入队。\n";
- ee=11; QQ->push(ee);
- ee=12; QQ->push(ee);
- ee=13; QQ->push(ee);
- ee=14; QQ->push(ee);
- ee=15; QQ->push(ee);
- cout << "队列的长度是" << QQ->size() << endl;
- QQ->printqueue();
- shmdt(QQ); // 把共享内存从当前进程中分离。
- }
复制代码 输出
- [root@localhost 07demosqueue]# g++ -o demo2 demo2.cpp _public.h _public.cpp
- [root@localhost 07demosqueue]# ./demo2
- 元素(1、2、3)入队。
- 队列的长度是3
- m_data[0],value=1
- m_data[1],value=2
- m_data[2],value=3
- 出队的元素值为1
- 出队的元素值为2
- 队列的长度是1
- m_data[2],value=3
- 元素(11、12、13、14、15)入队。
- 循环队列已满,入队失败。
- 队列的长度是5
- m_data[2],value=3
- m_data[3],value=11
- m_data[4],value=12
- m_data[0],value=13
- m_data[1],value=14
- [root@localhost 07demosqueue]# ./demo2
- 元素(1、2、3)入队。
- 循环队列已满,入队失败。
- 循环队列已满,入队失败。
- 循环队列已满,入队失败。
- 队列的长度是5
- m_data[2],value=3
- m_data[3],value=11
- m_data[4],value=12
- m_data[0],value=13
- m_data[1],value=14
- 出队的元素值为3
- 出队的元素值为11
- 队列的长度是3
- m_data[4],value=12
- m_data[0],value=13
- m_data[1],value=14
- 元素(11、12、13、14、15)入队。
- 循环队列已满,入队失败。
- 循环队列已满,入队失败。
- 循环队列已满,入队失败。
- 队列的长度是5
- m_data[4],value=12
- m_data[0],value=13
- m_data[1],value=14
- m_data[2],value=11
- m_data[3],value=12
复制代码 10. 信号量的根本概念
- 信号量本质上是一个非负数(>0)的计数器。
- 用于给共享资源创建一个标记,体现该共享资源被占用情况。
- P操作(wait)将信号量的值减1,如果信号量的值为0,将阻塞期待,直到信号量的值大于0。
- V操作(post) 将信号量的值加1,任何时间都不会阻塞。
- 如果约定信号量的取值只是0和1(0-资源不可用;1-资源可用)可以实现互斥锁。
- 如果约定信号量的取值体现可用资源的数量,可以实现生产/消费者模型。
- // 信号量。
- class csemp
- {
- private:
- union semun // 用于信号量操作的共同体。
- {
- int val;
- struct semid_ds *buf;
- unsigned short *arry;
- };
- int m_semid; // 信号量id(描述符)。
- // 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况,
- // 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢复为初始值。
- // 如果信号量用于互斥锁,设置为SEM_UNDO。
- // 如果信号量用于生产消费者模型,设置为0。
- short m_sem_flg;
- csemp(const csemp &) = delete; // 禁用拷贝构造函数。
- csemp &operator=(const csemp &) = delete; // 禁用赋值函数。
- public:
- csemp():m_semid(-1){}
- // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
- // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
- // 如果用于生产消费者模型,value填0,sem_flg填0。
- bool init(key_t key,unsigned short value=1,short sem_flg=SEM_UNDO);
- bool wait(short value=-1);// 信号量的P操作,如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
- bool post(short value=1); // 信号量的V操作。
- int getvalue(); // 获取信号量的值,成功返回信号量的值,失败返回-1。
- bool destroy(); // 销毁信号量。
- ~csemp();
- };
复制代码- // 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
- // 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
- // 如果用于生产消费者模型,value填0,sem_flg填0。
- bool csemp::init(key_t key,unsigned short value,short sem_flg)
- {
- if (m_semid!=-1) return false; // 如果已经初始化了,不必再次初始化。
- m_sem_flg=sem_flg;
- // 信号量的初始化不能直接用semget(key,1,0666|IPC_CREAT)
- // 因为信号量创建后,初始值是0,如果用于互斥锁,需要把它的初始值设置为1,
- // 而获取信号量则不需要设置初始值,所以,创建信号量和获取信号量的流程不同。
- // 信号量的初始化分三个步骤:
- // 1)获取信号量,如果成功,函数返回。
- // 2)如果失败,则创建信号量。
- // 3) 设置信号量的初始值。
- // 获取信号量。
- if ( (m_semid=semget(key,1,0666)) == -1)
- {
- // 如果信号量不存在,创建它。
- if (errno==ENOENT)
- {
- // 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。
- if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1)
- {
- if (errno==EEXIST) // 如果错误代码是信号量已存在,则再次获取信号量。
- {
- if ( (m_semid=semget(key,1,0666)) == -1)
- {
- perror("init 1 semget()"); return false;
- }
- return true;
- }
- else // 如果是其它错误,返回失败。
- {
- perror("init 2 semget()"); return false;
- }
- }
- // 信号量创建成功后,还需要把它初始化成value。
- union semun sem_union;
- sem_union.val = value; // 设置信号量的初始值。
- if (semctl(m_semid,0,SETVAL,sem_union) < 0)
- {
- perror("init semctl()"); return false;
- }
- }
- else
- { perror("init 3 semget()"); return false; }
- }
- return true;
- }
- // 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
- bool csemp::wait(short value)
- {
- if (m_semid==-1) return false;
- struct sembuf sem_b;
- sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。
- sem_b.sem_op = value; // P操作的value必须小于0。
- sem_b.sem_flg = m_sem_flg;
- if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }
- return true;
- }
- // 信号量的V操作(把信号量的值减value)。
- bool csemp::post(short value)
- {
- if (m_semid==-1) return false;
- struct sembuf sem_b;
- sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。
- sem_b.sem_op = value; // V操作的value必须大于0。
- sem_b.sem_flg = m_sem_flg;
- if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; }
- return true;
- }
- // 获取信号量的值,成功返回信号量的值,失败返回-1。
- int csemp::getvalue()
- {
- return semctl(m_semid,0,GETVAL);
- }
- // 销毁信号量。
- bool csemp::destroy()
- {
- if (m_semid==-1) return false;
- if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }
- return true;
- }
- csemp::~csemp()
- {
- }
复制代码 测试步调3
- // demo3.cpp,本程序演示用信号量给共享内存加锁。
- #include "_public.h"
- struct stgirl // 超女结构体。
- {
- int no; // 编号。
- char name[51]; // 姓名,注意,不能用string。
- };
- int main(int argc,char *argv[])
- {
- if (argc!=3) { cout << "Using:./demo no name\n"; return -1; }
- // 第1步:创建/获取共享内存,键值key为0x5005,也可以用其它的值。
- int shmid=shmget(0x5005, sizeof(stgirl), 0640|IPC_CREAT);
- if ( shmid ==-1 )
- {
- cout << "shmget(0x5005) failed.\n"; return -1;
- }
- cout << "shmid=" << shmid << endl;
- // 第2步:把共享内存连接到当前进程的地址空间。
- stgirl *ptr=(stgirl *)shmat(shmid,0,0);
- if ( ptr==(void *)-1 )
- {
- cout << "shmat() failed\n"; return -1;
- }
- // 创建、初始化二元信号量。
- csemp mutex;
- if (mutex.init(0x5005)==false)
- {
- cout << "mutex.init(0x5005) failed.\n"; return -1;
- }
- cout << "申请加锁...\n";
- mutex.wait(); // 申请加锁。
- cout << "申请加锁成功。\n";
- // 第3步:使用共享内存,对共享内存进行读/写。
- cout << "原值:no=" << ptr->no << ",name=" << ptr->name << endl; // 显示共享内存中的原值。
- ptr->no=atoi(argv[1]); // 对超女结构体的no成员赋值。
- strcpy(ptr->name,argv[2]); // 对超女结构体的name成员赋值。
- cout << "新值:no=" << ptr->no << ",name=" << ptr->name << endl; // 显示共享内存中的当前值。
- sleep(10);
- mutex.post(); // 解锁。
- cout << "解锁。\n";
- // 查看信号量 :ipcs -s // 删除信号量 :ipcrm sem 信号量id
- // 查看共享内存:ipcs -m // 删除共享内存:ipcrm -m 共享内存id
- // 第4步:把共享内存从当前进程中分离。
- shmdt(ptr);
- // 第5步:删除共享内存。
- //if (shmctl(shmid,IPC_RMID,0)==-1)
- //{
- // cout << "shmctl failed\n"; return -1;
- //}
- }
复制代码 输出
- [root@localhost 07demosqueue]# ./demo3 3 冰冰
- shmid=2
- 申请加锁...
- 申请加锁成功。
- 原值:no=0,name=
- 新值:no=3,name=冰冰
- 解锁。
复制代码 11.多历程的生产消费者模型
- //incache.cpp
- // 多进程的生产消费者模型的生产者程序
- #include "_public.h"
- int main()
- {
- struct stgirl // 循环队列的数据元素是超女结构体。
- {
- int no;
- char name[51];
- };
- using ElemType=stgirl;
- // 初始化共享内存。
- int shmid=shmget(0x5005, sizeof(squeue<ElemType,5>), 0640|IPC_CREAT);
- if ( shmid ==-1 )
- {
- cout << "shmget(0x5005) failed.\n"; return -1;
- }
- // 把共享内存连接到当前进程的地址空间。
- squeue<ElemType,5> *QQ=(squeue<ElemType,5> *)shmat(shmid,0,0);
- if ( QQ==(void *)-1 )
- {
- cout << "shmat() failed\n"; return -1;
- }
- QQ->init(); // 初始化循环队列。
- ElemType ee; // 创建一个数据元素。
- csemp mutex; mutex.init(0x5001); // 用于给共享内存加锁。
- csemp cond; cond.init(0x5002,0,0); // 信号量的值用于表示队列中数据元素的个数。
- mutex.wait(); // 加锁。
- // 生产3个数据。
- ee.no=3; strcpy(ee.name,"西施"); QQ->push(ee);
- ee.no=7; strcpy(ee.name,"冰冰"); QQ->push(ee);
- ee.no=8; strcpy(ee.name,"幂幂"); QQ->push(ee);
- mutex.post(); // 解锁。
- cond.post(3); // 实参是3,表示生产了3个数据。
- shmdt(QQ); // 把共享内存从当前进程中分离。
- }
复制代码- //outcache.cpp
- // 多进程的生产消费者模型的消费者程序
- #include "_public.h"
- int main()
- {
- struct stgirl // 循环队列的数据元素是超女结构体。
- {
- int no;
- char name[51];
- };
- using ElemType=stgirl;
- // 初始化共享内存。
- int shmid=shmget(0x5005, sizeof(squeue<ElemType,5>), 0640|IPC_CREAT);
- if ( shmid ==-1 )
- {
- cout << "shmget(0x5005) failed.\n"; return -1;
- }
- // 把共享内存连接到当前进程的地址空间。
- squeue<ElemType,5> *QQ=(squeue<ElemType,5> *)shmat(shmid,0,0);
- if ( QQ==(void *)-1 )
- {
- cout << "shmat() failed\n"; return -1;
- }
- QQ->init(); // 初始化循环队列。
- ElemType ee; // 创建一个数据元素。
- csemp mutex; mutex.init(0x5001); // 用于给共享内存加锁。
- csemp cond; cond.init(0x5002,0,0); // 信号量的值用于表示队列中数据元素的个数。
- while (true)
- {
- mutex.wait(); // 加锁。
- while (QQ->empty()) // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if
- {
- mutex.post(); // 解锁。
- cond.wait(); // 等待生产者的唤醒信号。
- mutex.wait(); // 加锁。
- }
- // 数据元素出队。
- ee = QQ->front(); QQ->pop();
- mutex.post(); // 解锁。
- // 处理出队的数据(把数据消费掉)。
- cout << "no=" << ee.no << ",name=" << ee.name << endl;
- usleep(100); // 假设处理数据需要时间,方便演示。
- }
- shmdt(QQ);
- }
复制代码 保举一个零声学院项目课,个人觉得老师讲得不错,分享给大家:
零声白金学习卡(含底子架构/高性能存储/golang云原生/音视频/Linux内核)
https://xxetb.xet.tech/s/3Zqhgt
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |