Linux系统编程——进程间通信
1.进程间通信概述
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
2.管道通信原理
一、管道
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
1.特点:
-
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
-
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
-
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
同一时间,单向;管道中的数据读走就没了
原型:
1 #include <unistd.h> 2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
要关闭管道只需将这两个文件描述符关闭即可。
3、例子
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。
3.管道编程实战
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fd[2];
pid_t pid;
char readbuf[128]= {"\0"};
if(pipe(fd) == -1){
printf("creat pipe failed\n");
}
pid = fork();
if(pid < 0){
printf("child failed!\n");
}else if(pid > 0){
sleep(3); //睡3秒,先执行子进程
printf("this is father\n");
close(fd[0]);
write(fd[1],"hello,son",strlen("hello,son"));
wait(NULL);
}else if(pid == 0){
printf("this is child \n"); //先执行子进程,打印输出
close(fd[1]); //关闭写
read(fd[0],readbuf,128); //read读不到内容阻塞在这,等待父进程休眠3秒后,写入内容再读取
printf("readbuf : %s\n",readbuf);
exit(0);
}
return 0;
}
运行结果:
4.创建命名管道
FIFO
FIFO,也称为命名管道,它是一种文件类型。
1、特点
- FIFO可以在无关的进程之间交换数据,与无名管道不同。
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2、原型
1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
- 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
- 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
//int mkfifo(const char *pathname, mode_t mode);
mkfifo("./file",0600);
return 0;
}
运行结果:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
int main()
{
//int mkfifo(const char *pathname, mode_t mode);
int ret = mkfifo("./file",0600);
if(ret == 0){
printf("creat success!\n");
}else if(ret == -1){
printf("creat failed\n");
perror("why");
}
return 0;
}
优化:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
int main()
{
//int mkfifo(const char *pathname, mode_t mode);
if((mkfifo("./file",0600)) == -1 && errno == EEXIST){ //EEXIST:表示文件已经存在
printf("creat failed!\n");
perror("why");
}else {
if(errno == EEXIST){
printf("has file\n");
}else{
printf("creat success\n");
}
}
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
int main()
{
//int mkfifo(const char *pathname, mode_t mode);
if((mkfifo("./file",0600)) == -1 && errno == EEXIST){ //EEXIST:表示文件已经存在
printf("creat failed!\n");
perror("why");
}
return 0;
}
运行结果:
5.命名管道的数据通信编程实现
read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
//int mkfifo(const char *pathname, mode_t mode);
char readbuf[30]={"\0"};
if((mkfifo("./file",0600)) == -1 && errno != EEXIST){
printf("creat failed!\n");
perror("why");
}
int fd = open("./file",O_RDONLY);
printf("read open success!\n");
int n_read = read(fd,readbuf,30);
printf("readbuf:%s\n",readbuf);
close(fd);
return 0;
}
write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main()
{
//int mkfifo(const char *pathname, mode_t mode);
char *str = "ni hao wa!";
int fd = open("./file",O_WRONLY);
printf("write open success\n");
int n_write = write(fd,str,strlen(str));
close(fd);
return 0;
}
先运行read.c ,再运行write.c
运行结果:
6.消息队列的通信原理
三、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1、特点
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2、原型
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);
在以下两种情况下,msgget将创建一个新的消息队列:
- 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
- key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
- type == 0,返回队列中的第一个消息;
- type > 0,返回队列中消息类型为 type 的第一个消息;
- type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。
7.消息队列相关api
2、原型
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);
在以下两种情况下,msgget将创建一个新的消息队列:
- 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
- key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
- type == 0,返回队列中的第一个消息;
- type > 0,返回队列中消息类型为 type 的第一个消息;
- type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。
8.消息队列编程收发数据
msgGet2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.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);
*/
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
typedef struct msgbuf Msgbuf;
int main()
{
Msgbuf readbuf ;
Msgbuf sendbuf = {988,"weclome your reach"};
int msgId = msgget(0x1234,IPC_CREAT|0777);
if(msgId == -1){
printf("creat msgId failed\n");
}
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0); // 0 默认 读不到 888 一直阻塞,直到读到为止
printf("read form queue:%s\n",readbuf.mtext);
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
return 0;
}
msgSend2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <string.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);
*/
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
typedef struct msgbuf Msgbuf;
int main()
{
Msgbuf sendbuf={888,"this is message form queue"};
Msgbuf readbuf = {988,"weclome your reach"};
int msgId = msgget(0x1234,IPC_CREAT|0777);
if(msgId == -1){
printf("creat msgId failed\n");
}
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),988,0);
printf("read form queue:%s\n",readbuf.mtext);
return 0;
}
运行结果:
9.键值生成及消息列队移除
ftok函数 生成 key
头文件
#include <[sys/types.h]
#include <sys/ipc.h>
函数原型:
```c
key_t ftok( const char * fname, int id )
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(“.”, 1); 这样就是将fname设为当前目录。
id是子序号。虽然是int类型,但是只使用8bits(1-255)。
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i
当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,因此与原来不同,所以得到的索引节点号也不同。
如果要确保key_t值不变,要么确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值,比如:
\#define IPCKEY 0x111
char path[256];
sprintf( path, "%s/etc/[config.ini](https://baike.baidu.com/item/config.ini?fromModule=lemma_inlink)", (char*)getenv("HOME") );
msgid=ftok( path, IPCKEY );[/code]
同一段程序,用于保证两个不同用户下的两组相同程序获得互不干扰的IPC键值。
由于etc/config.ini(假定)为应用系统的关键配置文件,因此不存在被轻易删除的问题——即使被删,也会很快被发现并重建(此时应用系统也将被重启)。
ftok()的设计目的也在于此。
msgctl函数
NAME
msgctl - message control operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf); //msgctl(msgid,IPC_RMID,NULL);
Valid values for cmd are:
IPC_STAT
Copy information from the kernel data structure associated
with msqid into the msqid_ds structure pointed to by buf. The
caller must have read permission on the message queue.
IPC_SET
Write the values of some members of the msqid_ds structure
pointed to by buf to the kernel data structure associated with
this message queue, updating also its msg_ctime member. The
following members of the structure are updated: msg_qbytes,
msg_perm.uid, msg_perm.gid, and (the least significant 9 bits
of) msg_perm.mode. The effective UID of the calling process
must match the owner (msg_perm.uid) or creator (msg_perm.cuid)
of the message queue, or the caller must be privileged.
Appropriate privilege (Linux: the CAP_IPC_RESOURCE capability)
is required to raise the msg_qbytes value beyond the system
parameter MSGMNB.
IPC_RMID //常用
Immediately remove the message queue, awakening all waiting
reader and writer processes (with an error return and errno
set to EIDRM). The calling process must have appropriate
privileges or its effective user ID must be either that of the
creator or owner of the message queue.
IPC_INFO (Linux-specific)
Returns information about system-wide message queue limits and
parameters in the structure pointed to by buf. This structure
is of type msginfo (thus, a cast is required), defined in
<sys/msg.h>
msgGet3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.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);
*/
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
typedef struct msgbuf Msgbuf;
int main()
{
Msgbuf readbuf ;
Msgbuf sendbuf = {988,"weclome your reach"};
int msgId = msgget(0x1234,IPC_CREAT|0777);
if(msgId == -1){
printf("creat msgId failed\n");
}
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0); // 0 默认 读不到 888 一直阻塞,直到读到为止
printf("read form queue:%s\n",readbuf.mtext);
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
msgSend3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <string.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);
*/
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
typedef struct msgbuf Msgbuf;
int main()
{
Msgbuf sendbuf={888,"this is message form queue"};
Msgbuf readbuf = {988,"weclome your reach"};
int msgId = msgget(0x1234,IPC_CREAT|0777);
if(msgId == -1){
printf("creat msgId failed\n");
}
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),988,0);
printf("read form queue:%s\n",readbuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
10.共享内存概述
类似一个箱子里,双方都能往箱子里放东西和取东西。
11.共享内存编程实现
什么是共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
- 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
- 因为多个进程可以同时操作,所以需要进行同步。
- 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2、原型
1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag); //内存大小最小1MB 1024
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr);
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
编码实现:
shmw.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// int shmget(key_t key, size_t size, int shmflg);
int shmid;
key_t key;
char *shmaddr;
//key_t ftok(const char *pathname, int proj_id);
key = ftok(".",897); //创建索引
printf("key = %x\n",key);
shmid = shmget(key,1024*4,IPC_CREAT|0666); //创建一个共享内存 大小为 1024*4 给予权限0666 0666中的666代表User、Group、及 //Other的权限分别是6,6,6,即均为rw权限。
if(shmid == -1){
printf("creat shmid failed!\n");
perror("why");
exit(-1);
}
shmaddr = shmat(shmid,0,0); //连接到共享内存
printf("shmat is ok!\n");
strcpy(shmaddr,"tihs is hahaha"); //写入数据到共享内存
sleep(5);
shmdt(shmaddr); //断开共享内存
shmctl(shmid,IPC_RMID,0); //关闭共享内存
printf("quit 2 \n");
return 0;
}
shmr.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// int shmget(key_t key, size_t size, int shmflg);
int shmid;
key_t key;
char *shmaddr;
//key_t ftok(const char *pathname, int proj_id);
key = ftok(".",897);
printf("key = %x\n",key);
shmid = shmget(key,1024*4,0); //获取一个共享内存
if(shmid == -1){
printf("creat shmid failed!\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0); //连接共享内存
printf("shmat is ok!\n");
printf("read shmaddr context:%s\n ",shmaddr);
shmdt(shmaddr); //断开共享内存
shmctl(shmid,IPC_RMID,0); //关闭共享内存
printf("quit\n");
return 0;
}
运行结果:
tips:
中途发现创建共享内存失败,后来发现是因为已经有了key值:
key = ftok(".",1);
改为:key = ftok(".",897);
就好了
icps -m
查看共享内存
icprm -m + 共享内存ID号
删除共享内存
12.信号概述
对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
信号概述
信号的意义是实现一些异步通信的手段!!
-
信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h
头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l
来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
-
信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
- 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是
SIGKILL
和SIGSTOP
)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景 - 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
- 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
具体的信号默认动作可以使用man 7 signal
来查看系统的具体定义。
信号存在优先级
了解了信号的概述,那么,信号是如何来使用呢?
其实对于常用的 kill 命令就是一个发送信号的工具,
kill 9 PID
来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
写一个死循环,用ps -aux|grep a.out
查看进程id号,用kill -9 +pid号
或者kill -SIGKILL +pid号
来结束程序,此处kill是命名
13.信号编程
signal函数
NAME
signal - ANSI C signal handling
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
使用:
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
printf("get signum = %d\n",signum);
printf("don't die\n");
}
int main()
{
// typedef void (*sighandler_t)(int); // 函数指针
// sighandler_t signal(int signum, sighandler_t handler);
signal(SIGINT,handler); //捕捉到SIGINT信号(ctrl+c),进行handler程序(这里修改了默认程序,所以只会运行修改的程序,不会进行默认程序, //也就是不会杀掉该进程),可采用 kill -9 +id号的方式结束该进程
while(1);
return 0;
}
运行结果:
ctrl + c 杀不掉该进程,
另一种写法:SIG_IGN忽略信号
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
printf("get signum = %d\n",signum);
printf("don't die\n");
}
int main()
{
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
// signal(SIGINT,handler);
signal(SIGINT,SIG_IGN); //SIG_IGN忽略信号
while(1);
return 0;
}
运行结果:
kill函数
NAME
kill - send signal to a process
SYNOPSIS
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
kill(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
mykill.c
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int agrc,char **argv)
{
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
int signum;
int pid;
signum = atoi(argv[1]); //atoi ASCII to int 将argv[1]转换为int型
pid = atoi(argv[2]);
kill(pid,signum);
return 0;
}
运行结果:
sprintf函数
sprintf指的是字符串格式化命令,函数声明为 int sprintf(char *string, char *format [,argument,...]);,主要功能是把[格式化]的数据写入某个[字符]串中,即发送[格式化]输出到 string 所指向的字符串。sprintf 是个[变参]函数。使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。解决这个问题,可以考虑使用 snprintf函数,该函数可对写入字符数做出限制。
函数声明
int sprintf(char *string, char *format [,argument,...]);
参数列表
- string– 这是指向一个字符数组的指针,该数组存储了 C 字符串。
- format– 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是**%[flags][width][.precision][length]specifier**
- *[argument]…*:根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。
功能
把格式化的数据写入某个字符串缓冲区。
返回值
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
sprintf 返回以format为格式argument为内容组成的结果被写入string的字节数,结束字符‘\0’不计入内。即,如果“Hello”被写入空间足够大的string后,函数sprintf 返回5
mykill2.c
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int agrc,char **argv)
{
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
int signum;
int pid;
char cmd[128]={0};
signum = atoi(argv[1]);
pid = atoi(argv[2]);
sprintf(cmd,"kill -%d %d",signum,pid); //sprintf 第一个参数:目标字符串 第二个参数:目标字符串的长相
// kill(pid,signum);
// system("kill -9 xxxx");
system(cmd);
return 0;
}
运行结果:
14.信号如何携带消息
#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只能任选其一
15.信号携带消息编程实战
sigactiondemo1.c
#include <stdio.h>
#include <signal.h>
//void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum,siginfo_t *info,void *context)
{
printf("get signum :%d\n",signum);
if(context != NULL){
printf("pid_t si_pid form= %d\n",info->si_pid);
printf("get data:%d\n",info->si_int);
printf("get data:%d\n",info->si_value.sival_int);
}
}
int main()
{
printf("pid = %d\n",getpid());
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
sigactionSend.c
#include <stdio.h>
#include <signal.h>
int main(int argc ,char **argv)
{
int pid;
int signum;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
//int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value);
printf("get pid = %d\n",getpid());
printf("done!\n");
return 0;
}
运行结果:
16.信号量概述
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、特点
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
- 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
- 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
- 支持信号量组。
2、原型
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags); //
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。
在semop函数中,sembuf结构的定义如下:
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
}
其中 sem_op 是一次操作中的信号量的改变量:
- 若sem_op > 0,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。
- 若sem_op < 0,请求 sem_op 的绝对值的资源。
- 如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。
- 当相应的资源数不能满足请求时,这个操作与sem_flg有关。
- sem_flg 指定IPC_NOWAIT,则semop函数出错返回EAGAIN。
- sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
- 当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
- 此信号量被删除,函数smeop出错返回EIDRM;
- 进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR
- 若sem_op == 0,进程阻塞直到信号量的相应值为0:
- 当信号量已经为0,函数立即返回。
- 如果信号量的值不为0,则依据sem_flg决定函数动作:
- sem_flg指定IPC_NOWAIT,则出错返回EAGAIN。
- sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
- 信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;
- 此信号量被删除,函数smeop出错返回EIDRM;
- 进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR
在semctl函数中的命令有多种,这里就说两个常用的:
- SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
- IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
17.信号量编程实现一
sem.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
// int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
//int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
// int semctl(int semid, int sem_num, int cmd, ...);
union semum
{
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) */
};
typedef union semum Semum;
int main(int aegc ,char **argv)
{
key_t key;
key = ftok(".",36);
int semid;
Semum initsem;
initsem.val = 1;
semid = semget(key,1,IPC_CREAT|0666); // 创建/获取信号量1:表示信号量集合中有一个信号量
semctl(semid,0,SETVAL,initsem);//初始化信号量 0:操作第0个信号量 SETVAL:设置信号量的值,设置为initsem
int pid = fork();
if(pid > 0){ //父进程
//去拿锁
printf("this is father!\n");
//锁放回去
}else if(pid == 0){ //子进程
printf("this is child\n");
}else{
printf("roek error\n");
}
return 0;
}
18.信号量编程实现二
sem3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
// int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
//int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
// int semctl(int semid, int sem_num, int cmd, ...);
union semum
{
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) */
};
void pGetKey(int semid)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(semid,&set ,1);
}
void vPutBackKey(int semid)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(semid,&set ,1);
}
typedef union semum Semum;
int main(int aegc ,char **argv)
{
key_t key;
key = ftok(".",36);
int semid;
Semum initsem;
initsem.val = 0;
semid = semget(key,1,IPC_CREAT|0666); // 创建/获取信号量1:表示信号量集合中有一个信号量
semctl(semid,0,SETVAL,initsem);//初始化信号量 0:操作第0个信号量 SETVAL:设置信号量的值,设置为initsem
int pid = fork();
if(pid > 0){ //父进程
//去拿锁
pGetKey(semid);
printf("this is father!\n");
//锁放回去
vPutBackKey(semid);
}else if(pid == 0){ //子进程
printf("this is child\n");
vPutBackKey(semid);
}else{
printf("roek error\n");
}
return 0;
}