Linux进程学习/复习
Linux进程与进程间通信
1.进程的概念
程序:二进制文件,占用的磁盘空间
进程:启动的程序
所有数据都在内存中
需要占用更多的系统资源
cpu,物理内存
1.1并行和并发
并发:在操作系统中,是指一个时间段中有几个程序都处于已启动到运行完毕之间,且这几个程序都是在同一个处理机上进行。(并发不是真正意义上同时进行,而是CPU分时执行各个程序)
并行:当系统有一个以上CPU时,一个CPU执行一个进程时,其他CPU也能执行其他进程,两个进程互不抢占CPU资源,可以同时进行,这种方式就成为并行。(决定并行的因素不是CPU数量,而是CPU的核心数量,比如一个CPU多个核也可以并行)
并行核并发的区别:1.并发是在同一时间段上内同时发生,并行是在同一时间点上同时进行。
2.并发的多个任务是互相抢占资源的。并行多个任务之间是不互相抢占资源的。
3.只有在多CPU或一个CPU多核的情况中,才会发生并行。
1.2进程控制块(PCB)
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关信息,linux内核的进程控制块是task_struct结构体。
//struct task_struct结构体内部成员有很多,重点掌握以下部分即可。
->进程id
->进程的状态
->进程切换时需要保护核恢复一些CPU寄存器
->描述虚拟地址空间的信息
->描述控制终端的信息
->当前工作目录
->umask掩码
->文件描述符,包含跟多指向file结构体的指针
->和信号相关的信息
->用户id和组id,stat
->会话和进程组
->进程可以适用的资源上限
1.3进程的状态
进程基本的状态有5种,初始态,就绪态,运行态,挂起态和终止态。
初始态为进程准备阶段,常常与就绪态结合来看。
就绪态:有执行资格没有执行权(等待CPU);
运行态:有执行资格有执行权(获得了CPU);
挂起态:没有执行资格没有执行权,条件满足后能够变成就绪态。
2.进程控制
1.fork函数
一个进程,包括代码、数据和分配给进程的资源。
fork()函数通过系统调用 创建一个与原来进程几乎完全相同的进程,也就是两个进程可以作完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的进程中,只有少数值与原来的进程不同。
包含的头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t fork(void);
两个返回值:
=0:当前进程为子进程
>0:当前进程为父进程
-1:出错。
基本使用示例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid>0)//father process
printf("this is father process %d\n",getpid());
else if(pid == 0)
printf("this is chiled process %d,ppid is %d\n",getpid(),getppid());
for(int i=0;i<4;i++){
printf("%d\n",i);
}
return 0;
}
/*
this is father process 47399
0
1
2
3
this is chiled process 47400,ppid is 47399
0
1
2
3
*/
fork()函数的补充
用户数据一样,进程ID不一样。
父子进程的执行顺序不一定
父子进程的应用:很多情况下有服务器和客户端,为了让客户端互不影响,服务器需要创建子进程来管理一个客户端。
2.ps和kill命令
ps 查看进程信息
参数:
a:现实现行终端机下所有程序,包括其他用户的程序
u:以用户为主的格式来显示程序状态
x:显示所有程序,不以终端机来区分
用的较多的命令:ps aux
和 ps ajx
kill 向指定的进程发送信号
结束一个进程:kill -9 pid号
3.父子进程间的数据共享
fork之后两个地址空间区数据完全相同
后续各自进行了不同的操作:
父进程:num--
子进程:num++
各个进程的地址空间中的数据是完全独立的。
对于同一个变量,读时共享,而写的时候分别在物理地址上拷贝一份变量进行单独读写。
那么父子进程之间可不可通过全局变量通信?答案是不能,因为两个进程内存不共享。
4.exec函数族
让父子进程来执行不相干的操作;
能够替换进程地址空间的代码.text段;
执行另外的程序,不需要创建额外的地址空间;
当前程序中调用另外一个应用程序。
//1.指定执行目录下的程序
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
/*
path:要执行程序的路径,最好是绝对路径
arg:要执行程序需要的参数
第一位arg:占位
后面的arg:命令的参数
参数写完之后:null
一般执行自己写的程序
*/
//2.执行PATH环境变量能够搜索到的程序
int execlp(const char *path, const char *arg, ...
/* (char *) NULL */);
返回值:
如果函数运行成功不返回
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
//father process
if(pid>0){
printf("this is father process %d\n",getpid());
}
//chiled process
else if(pid == 0){
execl("/bin/ls","ls","-l",NULL);
perror("execl");
printf("this is chiled process %d\n",getpid());
}
for(int j=0;j<3;j++){
printf("--j=%d\n",j);
}
return 0;
}
/*out:
this is father process 47957
i = 600
-- j = 0
-- j = 1
-- j = 2
total 16
-rwxrwxr-x 1 book book 8544 Jul 18 05:12 a.out
-rw-rw-r-- 1 book book 563 Jul 18 05:12 process.c
*/
从这段程序不难看出,子进程在执行了execl函数后,后面的代码段都没有再运行,而是执行了execl指定的程序。
5.孤儿进程和僵尸进程
**孤儿进程:**一个父进程退出,而他的一个或多个子进程还在运行,那么这些子进程就成为了孤儿线程。孤儿线程将被init进程(pid号为1)所收养,并由init进程时它们完成状态收集工作。
为什么会被init收养,为了释放子进程的占用的系统资源:
进程结束之后,能够释放用户区空间
释放不了PCB,必须由父进程释放
**僵尸进程:**当进程退出父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时,就会产生僵尸进程。僵尸进程会在以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid == 0){
//chiled process
printf("chiled process pid %d ppid %d\n",getpid(),getppid());
}
else if(pid>0){
//father process
while(1){
sleep(1);
printf("father process pid %d ppid %d\n",getpid(),getppid());
}
}
return 0;
}
//这里父进程没有对子进程进行资源回收,子进程就会变成一个僵尸进程。
僵尸进程是不能被 kill -9 pid
杀死的,因为已经是一个已经死掉了的进程,只是资源没有被回收。
通过ps aux 也是可以找到,如果进程末尾有defunct那么就是僵尸进程。
6.进程回收
wait阻塞函数
1.阻塞并等待子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)
pid_t wait(int *wstatus);
返回值:
-1:回收失败,已经没有子进程了
>0:回收子进程对应的pid
参数:
status判断子进程如何退出状态
1.WIFEXITED:为非0,进程正常结束
WEXITSTATUS(status);
如上宏为真,使用此宏,获取进程退出状态的参数,也就是return的值
2.WIFSIGNALED:为非0,进程一场退出
WTERMSIG(status);
如上宏为真,取得使进程种植的那个信号的编号
示例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid;
pid = fork();
if(pid == 0){//chiled process
while(1)
{
sleep(1);
printf("chiled pid is %d,ppid is %d\n",getpid(),getppid());
}
printf("chiled process pid %d,ppid is %d\n",getpid(),getppid());
}
else if(pid > 0){//father process
int status;
pid_t wpid;
wpid = wait(&status);
printf("wpid is %d\n",wpid);
if(WIFEXITED(status)){
printf("exit value is %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
printf("exit by signal is %d\n",WTERMSIG(status));
}
printf("father process pid %d,ppid %d\n",getpid(),getppid());
}
return 0;
}
waitpid函数
函数作用同wait函数
pid_t waitpid(pid_t pid,int *status,int options);
参数:
1.pid:指定回收某个子进程
pid == -1 回收所有子进程
while((wpid=waitpid(-1,status,0))!=-1)
pid > 0 回收某个pid相等的子进程
pid == 0 回收当前进程组的任一子进程
pid < 0 子进程的PID取反
2.status:子进程的退出状态,用法同wait函数
3.options:设置为WNOHANG,函数非阻塞,设置为0函数阻塞
返回值:
>0:返回清理掉的子进程ID
-1:回收失败,无子进程
如果为非阻塞
=0:参数3为WNOHANG,且子进程正在运行
wpid = waitpid(-1,&status,WNOHANG)
7.vfork创建进程
vfork也可以创建进程,与fork的区别:
1.vfork可以直接使用父进程存储空间,不拷贝
2.vfork可以保证子进程先运行,当子进程调用exit之后,父进程才执行
8.进程退出
1.正常退出
1.main函数调用return
2.进程调用exit(),标准C库
3.进程调用 _exit()或者 _Exit() 属于系统调用
补充:
4.进程最后一个线程返回
5.最后一个线程调用pthread_exit
2.异常退出
1.调用abort函数
2.当进程收到某些信号时,比如ctrl+C
3.最后一个线程时取消(cancellation) 请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码,这段代码和响应进程关闭所有打开描述符,释放它所使用的存储器等。
3.进程通信
1.IPC
IPC是什么?Inter Process Communication
进程间通信常用几种方法:
1、管道通信:有名管道,无名管道
2、信号 - 系统开销小
3、消息队列 - 内核的链表
4、信号量 - 计数器
5、共享内存
6、内核映射
7、套接字 - 稳定
2.无名管道
本质:内核缓冲区、伪文件-不占用磁盘空间
特点:
1.读端(read),写端(write),对应两个文件描述。数据写端流入,读端流出。
2.操作管道的进程被销毁后,管道自动释放。
3.管道默认是阻塞读写的。
管道的原理
内部实现方式:队列
环形队列
特点:先进先出
缓冲区大小:
默认4K
大小根据实际情况进行适当调整
管道的局限性
队列:
数据只可以读取一次,不可以重复读取
半双工:
单工:遥控器
半双工:对讲机,数据传输方向单向
全双工:电话
匿名管道:
适用于有血缘关系的进程
创建匿名管道
int pipe(int fd[2]);
fd-传出参数:
fd[0]-读端
fd[1]-写端
返回值:
0:成功
-1:创建失败
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int ret;
int fd[2];
ret = pipe(fd);
if(ret == -1){
printf("create pipe failed\n");
exit(1);
}
printf("pipe[0] is %d\n",fd[0]);
printf("pipe[1] is %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
父子进程使用管道通信
实现 ps aux | grep “bash”
要使用数据重定向:dup2
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1){
printf("pipe create failed!\n");
exit(1);
}
pid_t pid = fork();
if(pid == -1){
printf("fork failed\n");
exit(1);
}
//ps aux
//father process
if(pid > 0)
{
close(fd[0]);//关闭读端
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
perror("execlp");
exit(1);
}
//bash
//child process
if(pid == 0)
{
close(fd[1]);//关闭写端
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash",NULL);
}
close(fd[0]);
close(fd[1]);
return 0;
}
管道的读写行为
读操作:
->有数据
read(fd[1])正常读,返回读出的字节数
->无数据
写端被全部关闭,read返回0,相当于读文件到了尾部
没有全部关闭
read阻塞
写操作:
->读端全部关闭
管道破裂,进程被终止
内核给当前进程发送信号SIGPIPE-13,默认处理动作
->读端没全部关闭
缓冲区写满了
write阻塞
缓冲区没满
write继续写,知道写满,阻塞。
管道缓冲区的大小
命令:ulimit -a 可以看到pipe size
函数:long fpathconf(int fd, int name);
3.有名管道
int mkfifo(const char \*filename,mode_t mode);
功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关
返回值:创建成功返回0,创建失败返回-1。
1.特点
- 有名管道
- 在磁盘上有这样一个文件 ls -l ->p
- 也是一个伪文件,磁盘大小永久为0
- 数据存在内核中有一个对应的缓冲区
- 半双工通信方式
2.使用场景
没有血缘关系的进程间通信
3.创建方式
命令:mkfifo管道名
函数:mkfifo
4.fifo文件可以使用IO函数操作
open/close、read/write,不能执行lseek操作
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int ret;
ret = mkfifo("/home/book/abc/moumou/pid",0777);
if(ret == -1){
return -1;
}
printf("create fifo succeed\n");
return 0;
}
//执行这段代码后就会生成一个管道类型的文件
下面就写两个程序,用有名管道实现通信:
//readfifo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int ret;
int fd;
char readBuf[50] = {0};
int nread=0;
ret = mkfifo("./myfifo",0755);//创建有名管道
if(ret == -1){
return -1;
}
printf("mkfifo succeed\n");
fd = open("./myfifo",O_RDONLY);//打开有名管道文件,得到文件描述符
if(fd < 0){
return -2;
}
printf("open file succeed\n");
//read fifo
nread = read(fd,readBuf,50);
printf("read %d byte from fifo %s",nread,readBuf);
close(fd);
return 0;
}
//writefifo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd;
char *str = "hello world";
fd = open("./myfifo",O_WRONLY);
if(fd < 0){
return -1;
}
printf("open fifo succeed\n");
write(fd,str,strlen(str));
close(fd);
return 0;
}
4.消息队列
消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识。
特点
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
- 消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在
- 消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。
相关函数
1.#include <sys/msg.h>
2.int msgget(key_t key, int msgflg);
//创建或打开消息队列,成功返回队列ID,失败返回-1
参数:
msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key命名的消息队列不存在时创建一个消息队列。
返回值:成功返回队列ID,失败返回-1.
3.int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
//发送消息,成功返回消息数据的长度,失败返回-1
参数:
msgid:消息队列的ID
msgp:指向消息的指针,常用结构体msgbuf如下:
struct msgbuf{
long mtype;//消息类型
char mtext[N];//消息正文
}
size:发送的消息正文的字节数
flag:
IPC_NOWAIT 消息没有发送完成函数也会立即返回
0:直到发送完成函数才返回
返回值:
成功:0,失败:-1
4.ssize_t msgrcv(int msqid, void *msgp,size_t msgsz, long msgtyp, int msgflg);
//从一个消息队列中获取信息
参数:
msgid:消息队列的ID
msgp:要接收消息的缓冲区
size:要接受的消息的字节数
msgtype:
0 接收消息队列中的第一个消息
>0 接收消息队列中第一个类型为msgtype的消息
<0 接收消息队列中类型值不大于msgtype的绝对值且类型值又最小的消息。
flag
0:若无消息函数一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
返回值:
成功:接收到的消息长度
出错:-1
5.int msgctl(int msqid,int cmd,struct msqid_ds *buf);
//控制消息队列,成功返回0,失败返回-1
参数:
command 是将要采取的动作,它可以取3个值:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列当前关联值夫阿哥imsgid_ds值。
IPC_SET:如果进程有足够的权限,就把消息队列的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
6.key_t ftok(char * fname,int id);
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
fname就是你指定的文件名
id是子序号,虽然为int但只有8个bit能使用,即0~255
返回值:
执行成功,返回一个key_t值,失败返回-1
创建和删除消息队列
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
int main()
{
int msgid;
msgid = msgget(IPC_PRIVATE,0755);
if(megid == -1){
printf("create message queue failed\n");
return -1;
}
printf("create message queue succeed.msgid = %d\n",msgid);
system("ipcs -q");
msgctl(msgid,IPC_RMID,NULL);
system("ipcs -q");
return 0;
}
消息队列半双工通信:
//writemsg.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf;
int msgid;
key_t key;
int readret;
key = ftok(".",1);
msgid = msgget(key,IPC_CREAT|0755);
sendbuf.mtype = 100;
while(1)
{
memset(sendbuf.mtest,0,128);
printf("please input to message queue:\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
}
return 0;
}
//readmsg.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf readbuf;
int msgid;
key_t key;
int readret;
key = ftok(".",1);
msgid = msgget(key,IPC_CREAT|0755);
sendbuf.mtype = 100;
while(1)
{
memset(readbuf.mtest,0,128);
readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
printf("message is :%s\n",readbuf.mtest);
printf("rotal have %d byte\n",readret);
}
}
消息队列全双工通信
//msg_server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf,readbuf;
int msgid;
key_t key;
int readret;
pid_t pid;
key = ftok(".",1);//
msgid = msgget(key,IPC_CREAT|0755);
if(msgid == -1){
printf("create message queue failed\n");
return -1;
}
system("ipcs -q");
printf("create message queue succeed, msgid = %d\n",msgid);
//init msgbuf
sendbuf.mtype = 100;
pid = fork();
if(pid > 0)//father process write 100 msgtype
{
while(1)
{
memset(sendbuf.mtest,0,128);
printf("please input to message queue msgtype 100:\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
}
}
if(pid == 0)//child process read 200 msgtype
{
while(1)
{
memset(readbuf.mtest,0,128);
msgrcv(msgid,(void *)&readbuf,128,200,0);
printf("receive byte from message queue is :%s\n",readbuf.mtest);
}
}
return 0;
}
//msg_client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct msgbuf
{
long mtype;
char mtest[128];
char ID[4];
};
int main()
{
struct msgbuf sendbuf,readbuf;
int msgid;
key_t key;
int readret;
pid_t pid;
key = ftok(".",1);//
msgid = msgget(key,IPC_CREAT|0755);
if(msgid == -1){
printf("create message queue failed\n");
return -1;
}
system("ipcs -q");
printf("create message queue succeed, msgid = %d\n",msgid);
//init msgbuf
sendbuf.mtype = 200;
pid = fork();
if(pid > 0)//father process write 200 msgtype
{
while(1)
{
memset(sendbuf.mtest,0,128);
printf("please input to message queue msgtype 200:\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
}
}
if(pid == 0)//child process read 100 msgtype
{
while(1)
{
memset(readbuf.mtest,0,128);
msgrcv(msgid,(void *)&readbuf,128,100,0);
printf("receive byte from message queue is :%s\n",readbuf.mtest);
}
}
return 0;
}
5.共享内存
概念
共享内存(Shared Memory)就是允许多个进程同时访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其他的进程读到的数据也将改变。
特点:
- 共享内存创建之后,一直存在与内核中,知道被删除或系统关闭
- 共享内存和管道不一样,读取后,内容仍然存在共享内存中。
相关函数
1.int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
key:IPC_PRIVATE 或 ftok的返回值
size:共享内存的大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
成功:共享内存标识符--ID
错误:-1
2.void *shmat(int shm_id, const void *shm_addr,int shmflg);
//把共享内存连接映射到当前进程的地址空间
参数:
shm_id:ID号
shm_addr:映射到的地址,NULL为系统自动完成的映射
shmflg:
SHM_RDONLY共享内存只读
默认是0,表示共享内存可读写
返回值:
成功:映射后的地址
失败:NULL
3.int shmdt(const void *shmaddr)
//将进程里面的地址映射删除
参数:
shmid:要操作的共享内存标识符
返回值:
成功0;失败-1
4.int shmctl(int shm_id, int command, struct shmid_ds *buf);
//删除共享内存对象
参数:
shmid:要操作的共享内存标识符
cmd:
IPC_STAT(获取对象属性)--实现了命令ipcs -m
IPC_SET(设置对象属性)
IPC_RMID(删除对象)
buf:指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
成功0;出错-1。
进程间用共享内存通信
这里只介绍功能实现流程,对于错误提示就先不写进去了
//shm_write
int main()
{
int shmid;
int key;
char *p;
key = ftok(".",1); //得到key值
printf("ftok succeed, key:%x\n",key);
shmid = shmget(key,128,IPC_CREAT|0777);//创建共享内存
printf("create share memory succeed, shmid=%d\n",shmid);
//把共享内存映射到当前进程,NULL表示自动映射,0表示可读可写
p = (char *)shmat(shmid,NULL,0);
printf("please input to share memory:\n");
//write to share memory
fgets(p,128,stdin);
sleep(3);
shmdt(p);//将进程里面的地址映射删除
shmctl(shmid,IPC_RMID,0);//删除共享内存对象
return 0;
}
//shm_read
int main()
{
int shmid;
int key;
char *p;
key = ftok(".",1);
//write函数里面已经创建,所以这里最后参数写0就行,表示不用新建共享内存了
shmid = shmget(key,128,0);
p = (char *)shmat(shmid,NULL,0);
printf("share memory data:%s\n",p);
shmdt(p);
return 0;
}
6.信号
信号通信,就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。
内核可以发送多少信号呢?
kill -l //查看所有可以发送的信号
kill -9 pid //例如 终止进程信号给某个进程
信号通信的框架:
- 信号的发送(发送信号进程):kill raise alarm
- 信号的接收(接收信号进程):pause() sleep while(1)
- 信号的处理(接收信号进程):signal
1.信号的发送
kill:
所需头文件:
#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid,int sig);
参数:
pid:
正数:要接收信号的进程的进程号
0:信号发送到所有的pid进程,在同一进程组中的进程
-1:信号发送给所有进程表中的进程(出了进程号最大的进程)
sig:信号
返回值:
成功0;错误-1
raise
int raise(int sig);
//发送信号给自己==kill(getpid(),sig)
参数:
函数传入值:sig:信号
返回值:
成功0;出错-1
alarm
#include <unistd.h>。
alarm与raise函数比较:
相同点:让内核发送信号给当前进程。
不同点:1.alarm只会发送SIGALARM信号;2.alarm会让内核定时一段时间后发送信号,raise会让内核立刻发送信号。
unsigned int alarm(unsigned int seconds)
参数:
seconds:秒数
返回值:
成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0
出错-1
2.信号的接收
接收信号的进程,要有什么条件:要想使接收的进程能收到信号,这个进程不能结束:
sleep
pause:进程状态为S
int pause(void);
返回值:成功0,出错-1
3.信号的处理
收到信号的进程,应该怎么样处理?处理的方式:
- 1.进程的默认处理方式(内核为用户进程设置默认处理方式)A:忽略 B:终止进程 C:暂停
- 2.自己的处理方式:自己处理信号的方式告诉内核,这样你的进程收到这个信号就会采用自己的处理方式
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
参数:
signum:指定信号
handler:
SIG_IGN:忽略该信号
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针
返回值:
成功:设置之前的信号处理方式
出错:-1
signal函数有两个参数,第一个参数是整型变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;这个函数的返回值是一个函数指针。
7.信号灯semaphore
信号灯是信号量的一个集合,信号量的API函数是对单个信号进行操作,信号灯则是对一个信号集合操作。
int semget(key_t key,int nsems,int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
参数:
key:和信号灯集关联的key值
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限
返回值:
成功:信号灯集ID。失败-1
int semctl(int semid, int semnum, int cmd);
//控制信号量,删除信号量或初始化信号量
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
参数:
semid:信号灯集ID
semnum:要修改的信号灯编号
cmd:
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
函数返回值:
成功0;出错-1
PV操作
int semop(int semid,struct sembuf *sops,size_t nsops);
//用户改变信号量的值
使用示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>
#define SEM_READ 0
#define SEM_WRITE 0
union semun
{
int val;
};
void Poperation(int index,int semid)
{
struct sembuf sop;
sop.sem_num = index;
sop.sem_op = -1;
sop.sem_flg = 0;
semop(semid,&sop,1);
}
void Voperation(int index,int semid)
{
struct sembuf sop;
sop.sem_num = index;
sop.sem_op = 1;
sop.sem_flg = 0;
semop(semid,&sop,1);
}
int main()
{
key_t key;
key = ftok(".",123);
pid_t pid;
int semid;
int shmid;
char *shmaddr;
semid = semget(key,2,IPC_CREAT|0755);//2 signal
if(semid<0)
{
perror("semget");
return -1;
}
shmid = shmget(key,128,IPC_CREAT|0755);
if(shmid<0)
{
perror("shmget");
return -2;
}
//init semaphore
union semun myun;
//init semaphore read
myun.val = 0;
semctl(semid,SEM_READ,SETVAL,myun);
//init semaphore write
myun.val = 1;
semctl(semid,SEM_WRITE,SETVAL,myun);
pid = fork();
if(pid == 0)//child
{
while(1)
{
shmaddr = (char *)shmat(shmid,NULL,0);
Poperation(SEM_READ,semid);
printf("get share memory is: %s\n",shmaddr);
Voperation(SEM_WRITE,semid);
}
}
else if(pid > 0)//father
{
while(1)
{
shmaddr = (char *)shmat(shmid,NULL,0);
Poperation(SEM_WRITE,semid);
printf("please input to share memory:\n");
fgets(shmaddr,32,stdin);
Voperation(SEM_READ,semid);
}
}
return 0;
}