了解
创建进程后实现父子通讯的连接。
我们希望有一个管道来进行数据的交互。之前可以用exit和exec族函数来假通信。数据很有限。
所以我们使用IPC,进程的通信。
使用管道来通信
无名管道
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
https://blog.csdn.net/baidu_38621657/article/details/105724822>
怎么理解半双工呢:父写数据在管道,子进程读。在同一时间,父子只能一个读,一个写。
且!:管道中的数据读走之后就没有了。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
int pid;
char buf[128];
if(pipe(fd) == -1)
{
printf("创建管道失败!\n");
}
pid = fork();
if(pid < 0)
{
printf("进程创建失败!\n");
}
else if(pid > 0)
{
printf("我是董瑞龙的父亲\n");
close(fd[0]);
//ssize_t write(int fd, const void *buf, size_t count);
write(fd[1],"Ha Ha ha!\n",strlen("Ha Ha ha!"));
wait();
}
else
{
printf("董瑞龙我儿!\n");
close(fd[1]);
//ssize_t read(int fd, void *buf, size_t count);
read(fd[0],buf,128);
printf("read data is %s\n",buf);
exit(0);
}
return 0;
}
有名管道
FIFO、、使用mkfifo
与无名管道原理一样
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
- 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
- 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
这里要记住一个api:msgctl函数。作用是关闭队列。RMID:从内核中移除。
key 键值生成
ftok,去内核找到相关id 第一个是路径名,第二个是…随意
ls-ai id值通过十六进制计算。
各种通信方式的不同: 管道就像一个通道,信息就像上课传的小纸条(有去无回),比如你给女神表白她一直不回你。
消息队列就像一个箱子,可以放进去可以看,看完再把东西放进去。这种可以双方通信,比较优势。 共享内存是什么呢?
我可以给我的女神同桌在桌子上表白,女神可以马上看到。在内存中有一块公共内存,我和女神都可以看到。
共享内存
1:创建共享内存。
2:映射。(干掉)
1、shm函数(创建共享内存)
函数声明:
int shmget(key_t ey,size_t size, int shmflg);
第一个参数key:为共享内存段命名,有一个特殊的键值IPC_PRIVATE,用于创建一个只属于创建进程的共享内存
第二个参数size:是共享内存的大小(字节数)
第三个参数shmflg:包含9个比特的权限标志,作用同文件操作是的mode标志位相同
返回值:函数执行成功返回一个非负整数,即内存标识符,失败时返回-1.
2、shmat函数(将共享内存连接到进程)
第一次创建共享内存段,该共享内存段并不能被任何进程所使用,只有当共享内存被连接到一个进程,才可以访问共享内存,这个功能由shmat函数实现。
函数声明:
void *shmat(int shm_id, const oid *shm_addr, int shmflg);
第一个参数:所要连接的共享内存标识符。
第二个参数:指定共享内存连接到当前进程的地址位置,一般传NULL,表示由系统选择共享内存连接的位置。
第三个参数:可取SHM_RND和SHM_RDONLY(使得连接的内存地址只读)
返回值:函数执行成功返回共享内存的首地址,失败返回-1
3、shmdt函数(分离共享内存)
该函数的参数是shmat函数返回的共享内存地址指针,成功返回0,失败返回-1.
4、shmctl(控制函数)
函数声明:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数:共享内存标识符
第二个参数://要采取的动作,有如下取值:
IPC_STAT:把shm_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构体中的值
IPC_RMID:删除共享内存。
第三个参数:buf是一个指针,指向包含共享内存模式和访问权限的结构
返回值:成功返回0失败返回-1.
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
shmat使用时后面俩个变量写0,一个是让Linux自动分配共享内存,另一个是权限。
信号
类似于51的定时器,一种信号来中断你现在处理的事情,信号也有优先级。在Linux实际上是软中断,51是硬中断。
信号概述
1.信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
2.信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
- 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是
SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景 - 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
- 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。
其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps
命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是
9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
下面我们可以自己实现对Linux命令ctrl c的改写并通过软件编程实现对进程的kill。
#include <signal.h>
#include <stdio.h>
void change(int num)
{
printf("get num is = %d\n",num);
printf("quit is useless!");
}
int main()
{
signal(SIGINT,change);
while(1);
return 0;
}
这是阻止Linux shell命令ctrl c 的代码
那么我们如何自己去解锁呢?
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc , int **argv)
{
int num;
int pid;
num = atoi(argv[1]);
pid = atoi(argv[2]);
printf("argc is %d\n",argc);
printf("num=%d,pid=%d\n",num,pid);
kill(pid,num);
printf("welldone!\n");
return 0;
}
~ 这是删除命令
那么完成之后,删除命令也是可以优化的:使用system函数
信号如何携带消息
入门版:函数signal
高级版:函数sigaction
sigaction 的函数原型:创建
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用,第三个指针非空代表有数据。
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
信号发送函数:
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
下面是通过信号来实现消息的发送与接受:
#include <signal.h>
#include <stdio.h>
//int sigaction(int signum, const struct sigaction *act,
//struct sigaction *oldact);
void doo(int num,siginfo_t *info,void *context)
{
printf("get signum = %d\n",num);
if(context != NULL)
{
printf("get data = %d\n",info->si_int);
printf("get data = %d\n",info->si_value.sival_int);//俩种数据获取方式
printf("data from %d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("get pid = %d\n",getpid());
act.sa_sigaction = doo;
act.sa_flags = SA_SIGINFO;//可以接受到消息
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
这是收
#include <stdio.h>
#include <signal.h>
int main(int argc ,char **argv)
{
int num;
int pid;
num = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 520;
sigqueue(pid,num,value);
printf("getpid = %d\n",getpid());
return 0;
}
这是发,可以更改为字符串消息
信号量
信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
简单的说就是管理,比如控制共享内存一个读一个写,不同时进行。
信号量集。分为p操作和v操作。
1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);
1 struct sembuf
2 {
3 short sem_num; // 信号量组中对应的序号,0~sem_nums-1
4 short sem_op; // 信号量值在一次操作中的改变量
5 short sem_flg; // IPC_NOWAIT, SEM_UNDO
6 }
semget函数:
第二个参数是信号量集信号量的个数,第三个跟共享内存一样,可以使用IPC_CREAT|0600,
semctl函数:
第一个是操作量级id,第二代表要操作第几个信号量,第三个参数可以使用SETVAL来设置初值,根据man 要求定义联合体
union semun {
int val; /* Value for SETVAL */几个信号量
struct semid_ds buf; / Buffer for IPC_STAT, IPC_SET */
unsigned short array; / Array for GETALL, SETALL */
struct seminfo __buf; / Buffer for IPC_INFO
(Linux-specific) */
};
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/信号量编号
sbuf.sem_op = -1; /*P操作*/操作无法进入
sbuf.sem_flg = SEM_UNDO;//等待
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}