- Linux进程
- 进程概念
- 查看进程
- 进程创建(父进程与子进程)
- 进程状态
- 僵尸进程
- 孤儿进程
- 进程通信
- 概述
- 进程间通信和线程间通信区别
- 进程间通信方式
- 学习思路
- 无名管道
- 有名管道
- 信号
- 概念
- 信号的发送
- kill
- raise
- alarm
- 信号的接收
- pause
- 信号的处理
- signal
- ~~**sigaction**~~
- IPC通信
- 共享内存
- shmget
- ftok
- IPC对象的创建权限
- shmat
- shmdt
- shmctl
- 综合例子
- 有亲缘单向通信
- 无亲缘单向通信
- ~~**无亲缘双向通信**~~
- 消息队列
- 特点
- msgget
- msgctl
- msgsnd
- msgrcv
- 综合例子
- 无亲缘单向通信(同一个消息队列)
- 无亲缘双向通信(同一个消息队列)
- 信号灯
- 基本介绍
- semget
- semctl
- semop
- 综合例子
- 利用信号量实现一个进程中的多线程间同步
- 利用信号灯实现一个进程中的多线程间同步
- 利用信号灯实现两个无亲缘关系进程间同步
参考博客:
https://blog.csdn.net/skrskr66/article/details/89147940 、
https://zhuanlan.zhihu.com/p/96098130
从用户角度:进程就是一个正在运行中的程序,是程序正在运行的一个实例。
操作系统角度:操作系统运行一个程序,需要描述这个程序的运行过程,这个描述通过一个结构体task_struct{}来描述,统称为PCB,因此对操作系统来说进程就是PCB(process control block)程序控制块。
进程是一个正在执行的程序,是资源分配的最小单位。
输入ls /porc指令即可
前面蓝色数字代表的进程的ID。如果你想查看PID为1的进程信息,你需要查看/porc/1这个文件夹。
我们也可以使用ps -ef -aux指令来直接显示进程状态
还有getpid()和getppid()这两个函数用来查看当前程序的进程和父进程PID。
进程创建(父进程与子进程)在Linux里,除了进程0(即PID=0的进程)以外的所有进程都是由其他进程使用系统调用fork()创建的,这里调用fork()创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。
#includepid_t fork(void); 返回值: 父进程:返回值大于0; 子进程:返回值等于0
关于fork函数需要理解,每当调用一次fork函数时,会返回两次。一次是在调用进程中(父进程)返回一次,返回值是新派生的进程的进程ID(>0)。另一次是在子进程中返回,返回值是0,代表当前进程为子进程。如果返回-1,那么则代表在创建子进程的过程中出现了错误。
例子1
如上图所示,当fork()函数调用后,父进程中的变量pid赋值成子进程的pid(pid>0),所以父进程会执行else里的代码,打印出"This is the parent",而子进程的变量pid赋值成0,所以子进程执行if(pid == 0)里的代码,打印出"This is the child"
例子2
#include#include int main() { printf("parent pid:%dn",getpid()); int a = 100; pid_t pid = fork(); if(pid < 0) { return -1; } else if(pid == 0)//子进程中{ a = 20; printf("child !! pid:%d----a:%d--%pn",getpid(),a ,&a); } else//父进程 { sleep(1); printf("parent !! pid:%d----a:%d--%pn",getpid(), a, &a); } printf("nihaoa %dn",a); return 0; }
本例子因为有延时,先返回了子进程的pid,之后再返回了父进程的pid。fork()相当于创建了一个新的子进程,但是拷贝的是fork()函数之后的所有数据,之前的并不会拷贝。在代码之上就可以看到parentpid只打印了一次
注意:父进程子进程谁先运行?未规定。
总的来说:复制PCB,代码共享,但是子进程并非从头开始,而是从fork()函数之后开始,数据独有。
借用一下网上大佬对fork()的理解
(1)一个进程进行自身的复制,这样每个副本可以独立的完成具体的操作,在多核处理器中可以并行处理数据。这也是网络服务器的其中一个典型用途,多进程处理多连接请求。
(2)一个进程想执行另一个程序。比如一个软件包含了两个程序,主程序想调起另一个程序的话,它就可以先调用fork来创建一个自身的拷贝,然后通过exec函数来替换成将要运行的新程序。
那么创建子进程的意义是什么————压力分摊/干其他工作
在Linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的子进程。但是子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。当一个子进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
进程状态一个进程的生命周期可以划分为一组状态,这些状态刻画了整个进程。进程状态即体现一个进程的生命状态。一般来说,进程有五种状态:
- 创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
- 就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
- 执行状态:进程处于就绪状态被调度后,进程进入执行状态
- 阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
- 终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再次执行。
这五种状态的转换如图:
在Linux内核里,进程有时候也叫做任务,下面是状态在kernel源码里的定义:
static const char * const task_state_array[] = {
"R (running)",
"S (sleeping)",
"D (disk sleep)",
"T (stopped)",
"t (tracing stop)",
"X (dead)",
"Z (zombie)",
};
这些状态的具体含义是:
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- Z僵死状态(zombie):下文具体了解
这些当我们使用指令ps -aux就可以看到。
僵尸进程当一个子进程结束运行(一般是调用exit、运行时发生致命错误或收到终止信号所导致)时,子进程的退出状态(返回值)会回报给操作系统,系统则以SIGCHLD信号将子进程被结束的事件告知父进程,此时子进程的进程控制块(PCB)仍驻留在内存中。一般来说,收到SIGCHLD后,父进程会使用wait系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的PCB;而如若父进程没有这么做的话,子进程的PCB就会一直驻留在内存中,也即成为僵尸进程。
简单来说,当子进程先于父进程退出,但是父进程并没有调用wait或waitpid函数获取子进程的状态信息,子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。上文中提到的进程的僵死状态Z(zombie)就是僵尸进程对应的状态。
例子
#include#include #include #include int main() { pid_t pid; //循环创建子进程 while(1) { pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } else if (pid == 0) { printf("I am a child process.nI am exiting.n"); //子进程退出,成为僵尸进程 exit(0); } else { //父进程休眠20s sleep(20); continue; } } return 0; }
执行这上面这个程序,子进程中途退出了,Z+这个标志就是僵尸进程的标志。
其实,僵尸进程是有危害的。进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,当一个进程一直处于Z状态,那么它的PCB也就一直都要被维护。因为PCB本身就是一个结构体会占用空间,僵尸进程会造成资源浪费,所以我们应该避免僵尸进程的产生。
僵尸进程解决方案
僵尸进程解决方案参考链接
-
(1)kill杀死父进程(一般不用)
严格的说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵死进程的父进程。因此可以直接除掉父进程,通过kill发送SIGTERM或者SIGKILL信号。父进程结束后,僵尸进程进程变成孤儿进程,由init进程(进程号为1)充当父进程,并回收资源。
或者运行:kill -9 父进程的pid值 -
(2)父进程用wait或waitpid去回收资源(方案不好)
父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。 -
(3)通过信号机制,在处理函数中调用wait,回收资源
通过信号机制,子进程退出时向父进程发送SIGCHLD信号,父进程调用signal(SIGCHLD,sig_child)去处理SIGCHLD信号,在信号处理函数sig_child()中调用wait进行处理僵尸进程。什么时候得到子进程信号,什么时候进行信号处理,父进程可以继续干其他活,不用去阻塞等待。 -
(4)fork两次
fork两次,父进程fork一个子进程,子进程在fork出一个孙子进程,然后子进程立马退出,并由父进程去wait回收,这个过程不需要等待,然后父进程可以去干其他的活。孙子进程因为子进程退出会成为孤儿进程,那它可以由init进程(进程号为1)充当父进程,并回收。这样父进程和孙子进程就可以同时干活,互不影响,就实现了多进程。
当一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
例子
#include#include #include #include int main(){ pid_t pid; pid = fork(); if(pid < 0){ perror("fork error"); exit(1); } if(pid == 0){ printf("This is the child!n"); printf("pid = %d,ppid = %dn",getpid(),getppid();//父进程退出前的pid和ppid sleep(5); printf("npid = %d,ppid = %dn",getpid(),getppid());//父进程退出后的pid和ppid }else{ printf("This is the father!n"); sleep(1); printf("father process is exited!n"); } return 0; }
运行结果:
ubuntu@VM-0-7-ubuntu:~/c_practice$ ./orphan This is the father! This is the child! pid = 2338,ppid = 2337 father process is exited! ubuntu@VM-0-7-ubuntu:~/c_practice$ pid = 2338,ppid = 1
孤儿进程在父进程退出后会被init进程领养,直到自己运行结束为止。这个程序很容易理解,先输出子进程的pid和父进程的pid,再然后子进程开始睡眠父进程退出,这时候子进程变成孤儿进程,再次输出时,该进程的父进程变为init进程。
孤儿进程由于有init进程循环的wait()回收资源,因此并没有什么危害。
进程通信 概述 进程间通信和线程间通信区别-
进程通信
在用户空间实现进程通信是不可能的,需要通过Linux内核通信;//课程例子 //说明 在用户空间实现进程通信是不可能的 #include
#include #include #include int main() { int process_inter = 0; pid_t pid; pid=fork(); if(pid == 0) { int i=0; while(process_inter==0); for(i=0;i<5;i++) { printf("this is child:%dn",i); usleep(100); } } else if(pid > 0) { int i=0; for(i=0;i<5;i++) { printf("this is father:%dn",i); usleep(100); } process_inter=1; } while(1); return 0; } 执行结果:只有父进程打印,子进程没有打印。
-
线程间通信
可以在用户空间就可以实现,可以通过全局变量通信。//课程例子 //说明 在用户空间实现线程通信 #include
#include #include #include int process_inter; void *fun(void *var) { int j; while(process_inter==0); for(j=0;j<10;j++) { usleep(100); printf("this is fun j=%dn",j); } } int main() { int i; char str[]="hello linuxn"; pthread_t tid; int ret; ret=pthread_create(&tid,NULL,fun,(void *)str); if(ret<0) { printf("create thread failuren"); return -1; } for(i=0;i<10;i++) { usleep(100); printf("this is main fun i=%dn",i); } process_inter=1; while(1); return 0; } gcc thread.c -o thread -pthread
区别-lpthread和-pthread
-lpthread和-pthread区别参考链接- 添加额外的标志:在多数系统中,-pthread会被展开为“-D_REENTRANT -lpthread”,即是除了链接pthread库外,还先定义了宏_REENTRANT。定义这个宏的目的,是为了打开系统头文件中的各种多线程支持分支。
- 现在应该使用 -pthread 而不是 -lpthread。
- 单机模式(只有一个Linux内核)下的进程通信
- 管道通信(管道文件是一个特殊的文件,是由队列来实现的。)
无名管道、有名管道; - 信号通信
信号通信包括信号的发送、信号的接收和信号的处理; - IPC(Inter-Process Communication)通信
共享内存、消息队列和信号灯。
- 管道通信(管道文件是一个特殊的文件,是由队列来实现的。)
- Socket通信:存在于一个网络中两个进程之间的通信(两个Linux内核)。
每一种通信方式都是基于文件IO的思想。
- open:功能:创建或打开进程通信对象。函数形式不一样,有的是有多个函数完成。
- write: 功能:向进程通信对象中写入内容。函数形式可能不一样。
- read: 功能:从进程通信对象中读取内容。函数形式可能不一样。
- close: 功能:关闭或删除进程通信对象。形式可能不一样。
无名管道通信首先需要使用pipe函数创建管道之后,两个进程之间才能通信。
函数形式:
int pipe(int pipefd[2])
功能:创建管道,为系统调用:unistd.h
参数:
得到的两个文件描述符(pipefd[0]和pipefd[1]):
管道有一个读端pipefd[0]用来读和一个写端pipefd[1]用来写,这个规定不能变。
返回值:成功是0,出错是-1;
注意:
1).管道中的东西,读完了就删除了;满足队列的特点;
2).如果管道中没有东西可读,则会读阻塞;同理,存在写阻塞。
无名管道有一定的局限性。
1).属于半双工的通信方式;
2).只有具有“亲缘关系”的进程才能使用这种通信方式,也就是父进程和子进程之间。
验证写阻塞
计算出内核开辟的管道有多大。5456 5457
//课程例子 //验证写阻塞 #include#include #include #include int main() { int fd[2], ret, i; char writebuf[]="hello linux"; char readbuf[128]={0}; ret=pipe(fd); if(ret<0) { printf("create pip failuren"); return -1; } printf("create pip sucess fd[0]=%d,fd[1]=%dn",fd[0],fd[1]); while(i<5500) { write(fd[1],writebuf,sizeof(writebuf)); i++; } printf("write pip endn");//如果写阻塞,这句话不会打印 close(fd[0]); close(fd[1]); return 0; }
实现进程通信
// 课程例子 // 实现进程通信 #include有名管道#include #include #include int main() { int process_inter=0; pid_t pid; int ret; int fd[2]; ret =pip(fd); if(ret<0) { printf("create pip failuren"); return -1; } printf("create pip sucess fd[0]=%d,fd[1]=%dn",fd[0],fd[1]); pid=fork(); if(pid == 0) { int i=0; read(&fd[0],&process_inter,1);//if pip empty,sleep while(process_inter==0); for(i=0;i<5;i++) { printf("this is child:%dn",i); usleep(100); } } else if(pid > 0) { int i=0; for(i=0;i<5;i++) { printf("this is father:%dn",i); usleep(100); } process_inter=1; write(fd[1],&process_inter,1); } while(1); return 0; }
有名管道存在实际的物理文件,可以实现无亲缘关系的通信。
所谓的有名,即文件系统中存在这个一样文件节点,每一个文件节点都有一个inode号,而且这是一个特殊的文件类型:管道类型"p"。
UNIX下可以用ls -l 命令来看到文件的权限:
普通文件"-",目录文件"d",链接文件(软连接)“l”,命名管道文件"p",socket文件"s",字符设备文件"c",块设备文件"b"。
管道文件只有inode号,文件类型为p,但不占磁盘块空间,和套接字、字符设备文件、块设备文件一样。普通文件、链接文件及目录文件,不仅有inode号,还占磁盘块空间。
注意:
mkfifo()用来创建管道文件的节点,没有在内核中创建管道。只有通过open函数打开这个文件时才会在内核空间创建管道。
创建方法: #include#include int mkfifo(const char pathname, mode_t mode); 参数pathname:路径名,管道名称。 参数 mode:管道的权限,和umask有关系。 返回值:成功返回 0,错误返回-1。 删除方法: #include int unlink(const char *pathname);
通过有名管道实现无亲缘关系进程间通信
//课程例子 //通过有名管道实现无亲缘关系进程间通信 备注:本实验由mkfifo.c、first.c、second.c构成 // 文件1:mkfifo.c 创建管道文件 #include#include #include #include int main() { int ret=0; ret=mkfifo("./myfifo",0777) if(ret<0) { printf("create myfifo failuren"); return -1; } printf("create myfifo sucessn"); return 0; } //文件2:first.c #include #include #include #include #include int main() { int fd; int i=0; char process_inter=0; fd=open("myfifo",O_WRONLY); if(fd<0) { printf("open my fifo failuren"); return -1; } prrintf("open my fifo sucessn"); for(i=0;i<5;i++) { printf("this is father:%dn",i); usleep(100); } process_inter=1; sleep(5); write(fd,&process_inter,1); while(1); return 0; } //文件3:second.c #include #include #include #include #include int main() { int fd; int i=0; char process_inter=0; fd=open("myfifo",O_RDONLY); if(fd<0) { printf("open my fifo failuren"); return -1; } printf("open my fifo sucessn"); read(fd,&process_inter,1); while(process_inter==0); for(i=0;i<5;i++) { printf("this is father:%dn",i); usleep(100); } while(1); return 0; }
练习(方法如上):
有二个进程,一个server.c 一个client.c,要求server.c负责创建有名管道文件,即server要先运行,client后运行。
信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。
- (1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式;
- (2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件;
- (3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
查看当前系统可使用的信号有哪些?
kill -l
| 信号名 | 含义 | 默认操作 |
|---|---|---|
| SIGHUP | 该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。 | 终止 |
| SIGINT | 该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。 | 终止 |
| SIGQUIT | 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。 | 终止 |
| SIGILL | 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。 | 终止 |
| SIGFPE | 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。 | 终止 |
| SIGKILL | 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。 | 终止 |
| SIGALRM | 该信号当一个定时器到时的时候发出。 | 终止 |
| SIGSTOP | 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。 | 暂停进程 |
| SIGTSTP | 该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。 | 暂停进程 |
| SIGCHLD | 子进程改变状态时,父进程会收到这个信号。 | 忽略 |
| SIGABORT | 该信号用于结束进程。 | 终止 |
信号通信的框架
- 信号的发送(发送信号进程):kill、raise、alarm;
- 信号的接收(接收信号进程) : pause、sleep、while(1);
- 信号的处理(接收信号进程) : signal。
信号发送
头文件:
#include
#include
函数原型:
int kill(pid_t pid, int sig);
函数参数:
pid:指定进程。
正数:要接收信号的进程的进程号;
0:信号被发送到所有和pid进程在同一个进程组的进程;
-1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:要发送的信号
返回值:成功 0;失败 -1
课程例子
//文件名: mykill.c #include#include #include #include #include int main(int argc,char **argv) { int sig; int pid; if(argc<3) { printf("please input paramn"); return -1; } sig=atoi(argv[1]); pid=atoi(argv[2]); printf("sig=%d,pid=%dn",sig,pid); kill(pid,sig); return 0; }
命令行输入
raise./mykill pid(ps -aux 查看) 9
进程向自己发送信号,相当于kill(getpid(), sig)。
头文件:
#include
#include
函数原型:
int raise(int sig);
函数参数:
sig:要发送的信号
返回值:成功 0;失败 -1
例子1
//文件名: myraise.c #include#include #include #include #include int main(int argc,char **argv) { int sig; int pid; if(argc<2) { printf("please input paramn"); return -1; } sig=atoi(argv[1]); printf("sig=%dn",sig); printf("raise beforen"); raise(sig); //exit(0); //_exit(0); printf("raise endn"); return 0; }
例子2
//文件名: myraise2.c #includealarm#include #include #include #include int main() { pid_t pid; pid=fork(); if(pid>0) { sleep(8); while(1); } if(pid==0) { printf("raise beforen"); raise(SIGTSTP);//该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。 printf("raise endn"); exit(0); } return 0; } 运行结果: 父进程S、子进程T --8s后--> 父进程R、子进程T //文件名: myraise3.c #include #include #include #include #include #include int main() { pid_t pid; pid=fork(); if(pid>0)//父进程中,pid为子进程的pid { sleep(8); if(waitpid(pid,NULL,WNOHANG)==0)//非阻塞,子进程没有退出 { kill(pid,9); } // wait(NULL); while(1); } if(pid==0) { printf("raise beforen"); raise(SIGTSTP);//该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。 printf("raise endn"); exit(0); } return 0; } 运行结果: 父进程S、子进程T --8s后--> 父进程R、子进程Z 备注:如果加入wait(NULL),则子进程不会出现僵尸进程Z,此时运行结果: 父进程S、子进程T --8s后--> 父进程R 文件名: myraise4.c #include #include #include #include #include #include int main() { pid_t pid; pid=fork(); if(pid>0)//父进程中,pid为子进程的pid { waitpid(pid,NULL,0);//阻塞,子进程没有退出,等价于wait(NULL); printf("i am runningn"); while(1); } if(pid==0) { printf("raise beforen"); raise(SIGTSTP);//该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。 printf("raise endn"); exit(0); } return 0; }
alarm开启定时器,时间到后给调用alarm的进程发送一个SIGALRM信号。
默认情况下,会终止当前进程。
头文件:
#include
#include
函数原型:
unsigned int alarm(unsigned int seconds)
函数参数:
seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
//文件名: myalarm.c #include#include #include #include #include int main() { int i; i=0; printf("alarm beforen"); alarm(9); printf("alarm aftern"); while(i<20) { i++; sleep(1); printf("process things,i=%dn",i); } return 0; }
alarm 与 raise 函数的比较:
- 相同点:让内核发送信号给当前进程
- 不同点:
- alarm 只能发送SIGALARM信号;
- alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号。
接收信号的进程,要有什么条件:要想使接收的进程能收到信号,这个进程不能结束。
pause用于将调用进程挂起直到收到信号为止,进程状态为S。
头文件:
#include
函数原型:
int pause(void);
返回值:成功 0;失败 -1
//文件名: mypause.c #include信号的处理#include #include #include #include int main() { int i; i=0; printf("pause beforen"); pause(); printf("pause aftern"); while(i<20) { i++; sleep(1); printf("process things,i=%dn",i); } return 0; }
收到信号的进程,处理的方式:
- 进程的默认处理方式(内核为用户进程设置的默认处理方式)
- 忽略
- 终止进程
- 暂停
- 自定义处理方式
头文件:
#include
函数原型:
void (*signal(int signum, void (*handler)(int)))(int);
函数参数:
signum:指定信号
handler:
SIG_IGN:忽略该信号;
SIG_DFL:采用系统默认方式处理信号;
自定义的信号处理函数指针。
返回值:成功 设置之前的信号处理方式;失败 -1
signal函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;这个函数的返回值是一个函数指针。等价于如下形式:
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
//文件名: mysignal.c #include#include #include #include #include void myfun(int signum) { int i=0; while(i<10) { printf("process signal signum=%dn", signum); sleep(1); i++; } return ;//return main } int main() { int i; i=0; signal(14,myfun); printf("alarm beforen"); alarm(9); printf("alarm aftern"); while(i<20) { i++; sleep(1); printf("process things,i=%dn",i); } return 0; } //文件名: mysignal2.c #include #include #include #include #include #include void myfun(int signum) { int i=0; while(i<10) { printf("receive signal signum=%d,i=%dn", signum,i); sleep(1); i++; } return ;//return main } void myfun1(int signum) { printf("receive signal signum=%dn", signum); wait(NULL);//避免僵尸进程 return ;//return main } int main() { pid_t pid; pid=fork(); if(pid>0) { int i=0; signal(10,myfun); signal(17,myfun1); while(1) { printf("parent process things,i=%dn",i); sleep(1); i++; } } if(pid==0) { sleep(10); kill(getppid(),10);//父进程 sleep(10); exit(0);//发送了SIGCHLD 17,等价于kill(getppid(),17); } return 0; }
后面添加,没讲。
https://www.cnblogs.com/clno1/p/12941316.html
Inter-Process Communication。常见三种:共享内存、消息队列、信号灯。
-
常见问题:
- IPC对象肯定是存在于内核中。用户空间的文件系统中有没有IPC的文件类型?没有。
- 有名管道为什么能实现无亲缘关系的进程之间的通信?是因为用户空间有管道这种文件类型。
- IPC是不是只能用于亲缘关系进程之间的通信呢?肯定不是。
- IPC是怎样实现无亲缘关系之间的通信呢?也是保证用户空间的二个进程对内核中的同一个IPC对象的操作。(ftok)
-
三种IPC通信的操作函数
- 共享内存
- shmget:
创建或打开共享内存;
key参数:IPC_PRIVATE时实现有亲缘关系进程通信;ftok返回值时,能实现无亲缘关系进程通信。
shmflg参数:flag标志由两部分组成,一为IPC对象存取权限(含义同ipc_perm中的mode),一为IPC对象创建模式标志(IPC_CREAT、IPC_EXCL),两者进行|运算合成IPC对象创建权限。 - shmat:
建立共享内存到用户空间映射关系,读写通过标准io读写; - shmdt:
删除共享内存到用户空间映射关系; - shmctl:
共享内存删除,参数为IPC_RMID;
读取或修改共享内存的属性。
- shmget:
- 消息队列
- msgget:
创建或打开一个消息队列。key,flag同上。 - msgsnd
将新消息添加到消息队列。 - msgrcv
消息队列中数据读后,数据就不存在。 - msgctl
读取或修改消息队列的属性,或删除消息队列。
- msgget:
- 信号灯集
- semget:
创建或者打开一个信号灯集; - semop
P/V操作函数。 - semctl
用于控制信号灯集semid中的第semnum个信号灯。根据cmd的不同,可以有3或4个形参,第四个形参必须为联合体。
读取或修改信号灯的属性,或删除信号灯集。
- semget:
- 共享内存
创建或打开共享内存。
头文件: #include#include #include 函数原型: int shmget(key_t key, int size, int shmflg); 函数参数: key:IPC_PRIVATE 或 ftok的返回值 size:共享内存区大小 shmflg:同open函数的权限位,也可以用8进制表示法 返回值:成功:共享内存段标识符---ID---文件描述符;失败 -1
命令行查看和删除
ipcs –m
ipcrm -m shmid
使用IPC_PRIVATE创建的IPC对象, key值属性为0,和IPC对象的编号就没有了对应关系。这样毫无关系的进程,就不能通过key值来得到IPC对象的编号(因为这种方式创建的IPC对象的key值都是0)。因此,这种方式产生的IPC对象,和无名管道类似,不能用于毫无关系的进程间通信。
例子–IPC_PRIVATE
//文件名: myshmget.c #includeftok#include #include #include #include #include int main() { int shmid; shmid=shmget(IPC_PRIVATE,128,0777); if(shmid<0) { printf("create share memery failure!n"); return -1; } printf("create share memery sucess,shmid=%d!n",shmid); system("ipcs -m"); // system("ipcrm -m shmid"); return 0; } **直接运行有问题** > ipcrm: failed to parse argument: 'shmid'
函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。可实现无亲缘关系通信。
头文件:
#include < sys/types.h>
#include < sys/ipc.h>
函数原型:
char ftok(const char *path, char key )
参数:第一个参数:一个存在的文件或目录名,且可存取;
第二个参数:一个字符。
返回值:正确返回一个key值,出错返回-1
IPC_PRIVATE操作时,共享内存的key值都一样,都是0,所以使用ftok来创建key值。只要key值是一样的,用户空间的进程通过这个函数打开,则会对内核的同一个IPC对象操作。
IPC对象的创建权限msgget、semget、shmget函数最右边的形参flag(msgget中为msgflg、semget中为semflg、shmget中shmflg)为IPC对象创建权限,三种xxxget函数中flag的作用基本相同。
IPC对象创建权限(即flag)格式为0xxx,其中0表示8位制,低三位为用户、属组、其他的读、写、执行权限(执行位不使用)。IPC对象存取权限常与下面IPC_CREAT、IPC_EXCL两种标志进行或(|)运算完成对IPC对象创建的管理,在这里姑且把IPC_CREAT、IPC_EXCL两种标志称为IPC创建模式标志。下面是两种创建模式标志在
#define IPC_CREAT 01000 #define IPC_EXCL 02000
综上所述,flag标志由两部分组成,一为IPC对象存取权限(含义同ipc_perm中的mode),一为IPC对象创建模式标志(IPC_CREAT、IPC_EXCL),两者进行|运算合成IPC对象创建权限。
备注:IPC操作时IPC_CREAT和IPC_EXCL选项的说明。以共享内存为例:
当只有IPC_CREAT选项打开时,不管是否已存在该块共享内存,则都返回该共享内存的ID,若不存在则创建共享内存;
当只有IPC_EXCL选项打开时,不管有没有该快共享内存,shmget()都返回-1;
所以当IPC_CREAT | IPC_EXCL时, 如果没有该块共享内存,则创建,并返回共享内存ID。若已有该块共享内存,则返回-1。
第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,将共享内存映射到用户空间的地址中。
void *shmat (int shmid, const void *shmaddr, int shmflg); //malloc
参数:
第一个参数:shmid是由shmget()函数返回的共享内存标识;
第二个参数:shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址;
第三个参数shmflg:一组标志位,SHM_RDONLY表示共享内存只读,默认是0,表示共享内存可读写。
返回值:
成功:映射后的地址;失败:NULL。
共享内存特点:
- 共享内存创建之后,一直存在于内核中,直到被删除或系统关闭;
- 共享内存和管道不一样,读取后,内容仍在其共享内存中。
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。
将进程里的地址映射删除。
int shmdt(const void *shmaddr);
参数:
shmaddr 共享内存映射后的地址,即shmat()函数返回的地址指针。
返回值:
成功:0;出错:-1。
shmctl
易错: 容易敲错shmctl
与信号量的semctl()函数一样,用来控制共享内存。
删除共享内存对象,用IPC_RMID参数。
函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数参数:
shmid:要操作的共享内存标识符,即shmget()函数返回的共享内存标识符。
cmd : 要采取的操作
IPC_STAT (获取对象属性),等价于实现了命令实现了命令ipcs -m;
IPC_SET (设置对象属性);
IPC_RMID (删除对象) ,等价于实现了命令ipcrm -m shmid。
buf :指定IPC_STAT/IPC_SET时用以保存/设置属性。
函数返回值:
成功:0
出错:-1
shmid_ds结构 至少包括以下成员
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
//文件名: myshmctl.c #include#include #include #include #include #include int main() { int shmid; int key; char *p; key=ftok("./myshmget.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); shmid=shmget(key,128,IPC_CREAT | 0777); if(shmid<0) { printf("create share memery failure!n"); return -2; } printf("create share memery sucess,shmid=%d!n",shmid); p=(char *)shmat(shmid,NULL,0); if(p==NULL) { printf("shmat function failure!n"); return -3; } printf("shmat function sucessn"); system("ipcs -m"); // write share memory fgets(p,128,stdin); //read share memory printf("1,share memory data:%sn",p); printf("2,share memory data:%sn",p);//读两次 shmdt(p); //memcpy(p,"abcd",4);//映射关系删除,报错 shmctl(shmid,IPC_RMID,NULL); system("ipcs -m");//再次查看 // system("ipcrm -m shmid"); return 0; }
自己实现ipcrm命令
//文件名: myshmctl.c #include综合例子 有亲缘单向通信#include #include #include #include #include int main(int argc,char **argv) { int shmid; if(argc<3) { printf("please input paramn"); return -1; } if(strcmp(argv[1],"-m")==0) { printf("delete share memoryn"); } else { return -2; } shmid=stoi(argv[2]); printf("shmid=%dn",shmid); shmctl(shmid,IPC_RMID,NULL); system("ipcs -m");//再次查看 return 0; }
怎样通过共享内存实现有亲缘关系进程之间的单向通信框架?
注意:当使用IPC_PRIVATE 实现有亲缘关系进程之间通信时,fork()函数一定要放在shmget()函数之后。
// 文件名:total.c // 功能:共享内存 父子进程通信,可以不用ftok函数 #include无亲缘单向通信#include #include #include #include #include #include void myfun(int signum) { return; } int main() { int shmid; char *p; pid_t pid; shmid=shmget(IPC_PRIVATE,128,IPC_CREAT | 0777); if(shmid<0) { printf("create share memery failure!n"); return -2; } printf("create share memery sucess,shmid=%d!n",shmid); pid=fork(); if(pid>0)//父进程 { signal(SIGUSR2,myfun); p=(char *)shmat(shmid,NULL,0); if(p==NULL) { printf("parent process!!shmat function failure!n"); return -3; } while(1) { // write share memory printf("parent process!!start write share memory:n"); fgets(p,128,stdin); kill(pid,SIGUSR1); pause();//等待子进程读数据 } } if(pid==0)//子进程 { signal(SIGUSR1,myfun); p=(char *)shmat(shmid,NULL,0); if(p==NULL) { printf("child process!!shmat function failure!n"); return -3; } while(1) { pause();//等待父进程写数据 //read share memory printf("child process!!share memory data:%s",p); kill(getppid(),SIGUSR2); } } shmdt(p); shmctl(shmid,IPC_RMID,NULL); system("ipcs -m"); return 0; }
怎样通过共享内存实现无亲缘关系进程之间的单向通信框架?server–>client,可以通过1块共享内存实现
// 假设先运行server,再运行client // 文件名:server.c #include#include #include #include #include #include #include struct mybuf { int pid; char buf[124]; }; void myfun(int signum) { return; } int main() { int shmid; struct mybuf *p; int key; int pid; key=ftok("./myshmget.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); shmid=shmget(key,128,IPC_CREAT | 0777); if(shmid<0) { printf("create share memery failure!n"); return -2; } printf("create share memery sucess,shmid=%d!n",shmid); signal(SIGUSR2,myfun); p=(struct mybuf *)shmat(shmid,NULL,0); if(p==NULL) { printf("parent process!!shmat function failure!n"); return -3; } // 获得client pid p->pid=getpid();//write server pid to share memory pause();//wait client read server pid pid=p->pid; while(1) { // write share memory printf("parent process!!start write share memory:n"); fgets(p->buf,128,stdin); kill(pid,SIGUSR1); pause();//等待client进程读数据 } shmdt(p); shmctl(shmid,IPC_RMID,NULL); system("ipcs -m"); return 0; } // 文件名:client.c #include #include #include #include #include #include #include struct mybuf { int pid; char buf[124]; }; void myfun(int signum) { return; } int main() { int shmid; struct mybuf *p; int key; int pid; key=ftok("./myshmget.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); shmid=shmget(key,128,IPC_CREAT | 0777); if(shmid<0) { printf("create share memery failure!n"); return -2; } printf("create share memery sucess,shmid=%d!n",shmid); signal(SIGUSR1,myfun); p=(struct mybuf *)shmat(shmid,NULL,0); if(p==NULL) { printf("parent process!!shmat function failure!n"); return -3; } pid=p->pid;//read shared memory to get server pid p->pid=getpid();//write client pid to share memory kill(pid, SIGUSR2);//kill signal //client start reda data from shared memory while(1) { pause();//waite server write data to share memory printf("client process!!recive data from share memory data:%s",p->buf); kill(pid,SIGUSR2);//kill signal for server to write shared memory } shmdt(p); shmctl(shmid,IPC_RMID,NULL); system("ipcs -m"); return 0; }
怎样通过共享内存实现无亲缘关系进程之间的双向通信框架?可以通过2块共享内存实现。
后面补充。。。消息队列 特点
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。
- 与无名管道、有名管道一样,从消息队列中读出消息,消息队列中数据会被删除;
- 消息队列允许一个或多个进程同时读写消息,并且每条消息都有类型。由于消息可以定义很多类型,取出时可以指定哪个消息类型取出,所以消息队列解决了共享内存多个进程同时读写时数据混乱的问题,只要把消息标定不同的类型即可;
- 只有内核重启或人工删除时,该消息才会被删除,若不人工删除消息队列,消息队列会一直存在于内存中。
- 通过消息队列标识符来标识消息队列。消息队列在整个系统中是唯一的。
创建或打开一个消息队列。
msgget系统调用会首先在所有既有的消息队列中搜索与指定Ke值相同的队列,如果找到了,则返回该实例的标识符(在msgflg同时使用IPC_CREAT和IPC_EXCL会返回错误)。如果没有,且指定了IPC_CREAT,那么就会创建一个新队列,并返回该队列的标识符。
头文件: #include#include #include 函数原型: int msgget(key_t key, int flag); 函数参数: key:和消息队列关联的key值 可以指定为IPC_PRIVATE用于父子进程间通信); 或者 ftok()函数创建。 flag:消息队列的访问权限 IPC_CREAT:创建; IPC_EXCEL:如果已经存在则返回失败; 此外,如果是创建消息队列时,还需要额外加上该消息队列实例对不同用户的权限,比如 | 0600。否则会导致访问权限错误。 返回值:成功:消息队列ID(非零整数);失败 -1
命令行查看和删除
msgctlipcs –q
ipcrm -q msgqid
易错: 容易敲错msgctl
对消息队列进行各种控制,如修改消息队列的属 性,或删除消息消息队列。
头文件: #includemsgsnd#include #include 函数原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); 函数参数: msqid:消息队列的队列ID cmd: IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中; IPC_SET:设置消息队列的属性。这个值取自buf参数; IPC_RMID:从系统中删除消息队列。 buf:消息队列缓冲区。msqid_ds数据类型的地址,用来存放或更改 消息队列的属性 返回值:成功:0;失败 -1
易错: 容易敲错msgsnd
将新消息添加到消息队列。
头文件: #include#include #include 函数原型: int msgsnd(int msqid, const void *msgp, size_t size, int flag); 函数参数: msqid:消息队列的ID msgp:指向待发送消息结构体的地址。 size:发送的消息正文的字节数。 flag: IPC_NOWAIT:消息没有发送完成函数也会立即返回; 0:阻塞直到发送完成函数才返回。 返回值: 成功:0;失败 -1 备注: 1、要发送的消息存放在 msgbuf 结构体中,使用 msgp 指针指向该类型引用消息数据的内容和消息的类型。常用消息结构msgbuf如下: struct msgbuf { long mtype; //消息类型 char mtext[N]; //消息正文 }; 2、size是msgp指向的消息正文的长度,而不是整个结构体的长度,size是不包括长整型"消息类型"成员变量的长度。
如果消息队列中有足够的空间,msgsnd()函数会立即返回,并实现发送消息到msgp指定的消息队列。
如果消息队列已满,msgflg 参数没有设置 IPC_NOWAIT 值,则 msgsnd() 函数将阻塞;如果设置了 IPC_NOWAIT 值,则msgsnd()函数调用失败,并返回 -1,直到消息队列中有空间时,函数才返回0。
消息队列中数据读后,数据就不存在了。
头文件: #include#include #include 函数原型: int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag); 函数参数: msqid:消息队列的ID msgp:接收消息的缓冲区 size:要接收的消息的字节数 msgtype: 0:接收消息队列中第一个消息; 大于0:接收消息队列中第一个类型为msgtyp的消息; 小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。 flag: 0:若无消息函数会一直阻塞 IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。 返回值:成功:接收到的消息的长度;失败 -1
//文件名: msgrcv.c #include综合例子 无亲缘单向通信(同一个消息队列)#include #include #include #include #include #include struct msgbuf { long type; char voltage[124]; char ID[4]; }; int main() { int readret; int msgqid; struct msgbuf sendbuf,rcvbuf; msgqid=msgget(IPC_PRIVATE,IPC_CREAT | 0777); if(msgqid<0) { printf("create message queue failure!n"); return -1; } printf("create message queue sucess,msgqid=%d!n",msgqid); system("ipcs -q"); //init sendbuf sendbuf.type=100; printf("please input a message:n"); fgets(sendbuf.voltage,124,stdin); //start write message to message queue msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0); // init rcvbuf memset(rcvbuf.voltage,0,124); // start read message queue readret=msgrcv(msgqid,(void *)&rcvbuf,124,100,0); printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage); //delete message queue msgctl(msgqid,IPC_RMID,NULL); system("ipcs -q"); return 0; }
// 文件名: msgserver.c #include无亲缘双向通信(同一个消息队列)#include #include #include #include #include #include struct msgbuf { long type; char voltage[124]; char ID[4]; }; int main() { int key; int readret; int msgqid; struct msgbuf sendbuf,rcvbuf; key=ftok("./msgget.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); msgqid=msgget(key,IPC_CREAT | 0777); if(msgqid<0) { printf("create message queue failure!n"); return -1; } printf("create message queue sucess,msgqid=%d!n",msgqid); system("ipcs -q"); sendbuf.type=100;//init sendbuf type while(1) { memset(sendbuf.voltage,0,124); printf("please input a message:n"); fgets(sendbuf.voltage,124,stdin); //start write message to message queue msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0); } //delete message queue msgctl(msgqid,IPC_RMID,NULL); system("ipcs -q"); return 0; } // 文件名:msgclient.c #include #include #include #include #include #include #include struct msgbuf { long type; char voltage[124]; char ID[4]; }; int main() { int key; int readret; int msgqid; struct msgbuf sendbuf,rcvbuf; key=ftok("./msgget.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); msgqid=msgget(key,IPC_CREAT | 0777); if(msgqid<0) { printf("create message queue failure!n"); return -1; } printf("create message queue sucess,msgqid=%d!n",msgqid); system("ipcs -q"); rcvbuf.type=100;//init rcvbuf type while(1) { memset(rcvbuf.voltage,0,124); // start read message queue readret=msgrcv(msgqid,(void *)&rcvbuf,124,rcvbuf.type,0); printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage); } //delete message queue msgctl(msgqid,IPC_RMID,NULL); system("ipcs -q"); return 0; }
双向 同时收发
// 文件名: msgserver1.c #include信号灯 基本介绍#include #include #include #include #include #include struct msgbuf { long type; char voltage[124]; char ID[4]; }; int main() { int key; int readret; int msgqid; struct msgbuf sendbuf,rcvbuf; pid_t pid; key=ftok("./msgctl.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); msgqid=msgget(key,IPC_CREAT | 0777); if(msgqid<0) { printf("create message queue failure!n"); return -1; } printf("create message queue sucess,msgqid=%d!n",msgqid); system("ipcs -q"); //在消息队列之后 fork函数创建子进程 pid=fork(); if(pid>0)//父进程 { //负责写 sendbuf.type=100;//init sendbuf type while(1) { memset(sendbuf.voltage,0,124); printf("please input a message:n"); fgets(sendbuf.voltage,124,stdin); //start write message to message queue msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0); } } if(pid==0)//子进程 { //负责读 rcvbuf.type=200;//init rcvbuf type while(1) { memset(rcvbuf.voltage,0,124); // start read message queue readret=msgrcv(msgqid,(void *)&rcvbuf,124,rcvbuf.type,0);//阻塞方式读 printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage); } } //delete message queue msgctl(msgqid,IPC_RMID,NULL); system("ipcs -q"); return 0; } // 文件名: msgclient1.c #include #include #include #include #include #include #include struct msgbuf { long type; char voltage[124]; char ID[4]; }; int main() { int key; int readret; int msgqid; struct msgbuf sendbuf,rcvbuf; pid_t pid; key=ftok("./msgctl.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); msgqid=msgget(key,IPC_CREAT | 0777); if(msgqid<0) { printf("create message queue failure!n"); return -1; } printf("create message queue sucess,msgqid=%d!n",msgqid); system("ipcs -q"); //在消息队列之后 fork函数创建子进程 pid=fork(); if(pid==0)//子进程 { //负责写 sendbuf.type=200;//init sendbuf type while(1) { memset(sendbuf.voltage,0,124); printf("please input a message:n"); fgets(sendbuf.voltage,124,stdin); //start write message to message queue msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0); } } if(pid>0)//父进程 { //负责读 rcvbuf.type=100;//init rcvbuf type while(1) { memset(rcvbuf.voltage,0,124); // start read message queue readret=msgrcv(msgqid,(void *)&rcvbuf,124,rcvbuf.type,0);//阻塞方式读 printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage); } } //delete message queue msgctl(msgqid,IPC_RMID,NULL); system("ipcs -q"); return 0; }
1.信号灯和POSIX规范中的信号量之间的区别是什么呢?
POSIX信号量针对于单个信号量sem_init、sem_wait、sem_post,即POSIX规范中的信号量只能作用于一个信号量(当然这个信号量对应的资源可能不止一个),每个信号对应一个P操作和V操作;而System V信号灯是一个集合,它是信号灯的集合,它含有多个信号量,它可以对多个信号量同时进行P/V操作。
2.为什么需要信号灯,只有POSIX信号量不是足够了吗?
只有POSIX信号量是不够的,考虑这样一个场景:
- 线程A中和线程B都需要访问共享资源1和共享资源2。线程A中会需要先申请共享资源1,然后再申请共享资源2;但是在线程B中,会先申请共享资源2,然后再申请共享资源1;当线程A中开始申请共享资源1时,紧接着会申请共享资源2;而此时线程B中开始申请共享资源2时,紧接着会申请共享资源1;线程B正在占用着共享资源2,线程A正在占着共享资源1,导致线程B申请不到共享资源1,它就不会释放共享资源2;线程A申请不到共享资源2,它就不会释放共享资源1;这样就造成了死锁。
- 当然这种情况可以这样避免,每次申请完一个共享资源时,申请结束就释放,而不能同时申请多个共享资源,多个申请成功之后,在释放。当然也可以使用今天的信号灯来解决这个问题。
信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。信号灯相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。
信号灯集合(可以包含多个信号量),IPC对象是一个信号灯集(多个信号量)。
| 功能 | 信号量(POSIX) | 信号灯集(IPC) |
|---|---|---|
| 定义信号变量 | sem_t sem1; | semget |
| 初始化信号量 | sem_init | semctl |
| P操作 | sem_wait | semop |
| V操作 | sem_post | semop |
创建或者打开一个信号灯集(注意:是信号灯集,不是信号灯!)
头文件: #include#include #includde 函数原型: int semget(key_t key, int nsems, int semflag) 函数参数: key 和信号灯集关联的key值 可以指定为IPC_PRIVATE用于父子进程间通信); 或者 ftok()函数创建。 nsems: 信号灯集中包含的信号灯数目 semflg:信号灯集的访问权限 返回值:成功:信号灯集ID;失败 -1
命令行查看和删除
semctlipcs –s
ipcrm -s semid
用于控制信号灯集semid中的第semnum个信号灯。根据cmd的不同,可以有3或4个形参,第四个形参必须为联合体。
头文件: #include#include #includde 函数原型: int semctl ( int semid, int semnum, int cmd,...union semun arg(不是地址)); 函数参数: semid:信号灯集ID semnum: 要修改的信号灯集编号,删除操作时,这个值可以设置为任意值; cmd: GETVAL:获取信号灯的值; SETVAL:设置信号灯的值; IPC_RMID:从系统中删除信号灯集合; union semun { int val; //SETVAL:设置信号灯的值 struct semid_ds *buf;// IPC_STAT (获取对象属性) //IPC_SET (设置对象属性) unsigned short *array; struct seminfo *__buf; }; 返回值:成功:0;失败 -1
//文件名: semctl.c #include#include #include #include #include #include int main() { int semid; semid=semget(IPC_PRIVATE,3,IPC_CREAT | 0777); if(semid<0) { printf("create sem failure!n"); return -1; } printf("create sem sucess,semid=%d!n",semid); system("ipcs -s"); //delete semaphore semctl(semid,0,IPC_RMID,NULL); system("ipcs -s"); return 0; }
semopSETVAL:设置信号灯的值 信号灯的初始化,类似sem_init;
P/V操作函数
头文件: #include综合例子 利用信号量实现一个进程中的多线程间同步#include #includde 函数原型: int semop ( int semid, struct sembuf *opsptr, size_t nops); 函数参数: semid:信号灯集ID struct sembuf { short sem_num; // 要操作的信号灯的编号 short sem_op; // 0 : 等待,直到信号灯的值变成0 // 1 : 释放资源,V操作 // -1 : 分配资源,P操作 short sem_flg; // 0阻塞, IPC_NOWAIT非阻塞, SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。 }; nops: 要操作的信号灯的个数 返回值:成功:0;失败 -1
//文件名: semaphore.c //利用信号量实现一个进程中的多线程间同步 #include利用信号灯实现一个进程中的多线程间同步#include #include #include #include struct sem_t sem; void *fun(void *var) { int j; //p sem_wait(&sem);//sleep for(j=0;j<10;j++) { usleep(100); printf("this is fun j=%dn",j); } } int main() { int i; char str[]="hello linuxn"; pthread_t tid; int ret; sem_init(&sem,0,0);//初始化信号量为0 ret=pthread_create(&tid,NULL,fun,(void *)str); if(ret<0) { printf("create thread failuren"); return -1; } for(i=0;i<10;i++) { usleep(100); printf("this is main fun i=%dn",i); } //v sem_post(&sem); while(1); return 0; }
//文件名: sem.c //利用信号灯实现一个进程中的多线程间同步 #include#include #include #include #include #include union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; union semun mysemun; struct sembuf mysembuf; int semid; void *fun(void *var) { int j; //p mysembuf.sem_op=-1; semop(semid,&mysembuf,1); for(j=0;j<10;j++) { usleep(100); printf("this is fun j=%dn",j); } } int main() { int i; char str[]="hello linuxn"; pthread_t tid; int ret; //create sem semid=semget(IPC_PRIVATE,3,IPC_CREAT | 0777); if(semid<0) { printf("create sem failure!n"); return -1; } printf("create sem sucess,semid=%d!n",semid); //init sem mysemun.val=0; semctl(semid,0,SETVAL,mysemun); //init mysembuf mysembuf.sem_num=0; mysembuf.sem_flg=0; ret=pthread_create(&tid,NULL,fun,(void *)str); if(ret<0) { printf("create thread failuren"); return -1; } for(i=0;i<10;i++) { usleep(100); printf("this is main fun i=%dn",i); } //v mysembuf.sem_op=1; semop(semid,&mysembuf,1); while(1); return 0; }
注意: mysembuf.sem_flg 容易敲错
利用信号灯实现两个无亲缘关系进程间同步//备注:程序运行顺序:sem2-->sem1; // 打印顺序:sem1-->sem2 //文件名: sem1.c #include#include #include #include #include union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; union semun mysemun; struct sembuf mysembuf; int semid; int main() { int i; char str[]="hello linuxn"; int ret; int key; //create key and sem key=ftok("./sem.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); semid=semget(key,3,IPC_CREAT | 0777); if(semid<0) { printf("create sem failure!n"); return -2; } printf("create sem sucess,semid=%d!n",semid); //sem2先运行,已经初始化了sem // //init sem // mysemun.val=0; // semctl(semid,0,SETVAL,mysemun); //init mysembuf mysembuf.sem_num=0; mysembuf.sem_flg=0; for(i=0;i<10;i++) { usleep(100); printf("this is sem1 fun i=%dn",i); } //sem1先打印,打印结束,进行v操作。 //v mysembuf.sem_op=1; semop(semid,&mysembuf,1); while(1); return 0; } //文件名: sem2.c #include #include #include #include #include union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; union semun mysemun; struct sembuf mysembuf; int semid; int main() { int i; char str[]="hello linuxn"; int ret; int key; //create key and sem key=ftok("./sem.c",'a'); if(key<0) { printf("create key failure!n"); return -1; } printf("create key sucess,key=%d!n",key); semid=semget(key,3,IPC_CREAT | 0777); if(semid<0) { printf("create sem failure!n"); return -2; } printf("create sem sucess,semid=%d!n",semid); //sem2代码先运行,先初始化sem,然后p操作。 //init sem mysemun.val=0; semctl(semid,0,SETVAL,mysemun); //init mysembuf mysembuf.sem_num=0; mysembuf.sem_flg=0; //p mysembuf.sem_op=-1; semop(semid,&mysembuf,1); for(i=0;i<10;i++) { usleep(100); printf("this is sem2 fun i=%dn",i); } while(1); return 0; }



