一.管道通信
1.什么是管道:
管道是单向的、先进先出的,它是把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道尾部写入数据,另一个进程(读进程)从管道的头部读出数据
2.popen:两个程序之间传递数据的一种简单方法是使用popen和pclose。
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
command字符串是要运行的程序名和相应的参数。type必须是"r"或"w"。
- 如果type是"r",被调程序的输出就可以被调用程序使用,调用程序利用popen函数返回的FILE *文件流指针,可以读取被调程序的输出;如果type是"w",调用程序就可以向被调程序发送数据,而被调程序可以在自己的标准输入上读取这些数据。
- pclose函数只在popen启动的进程结束后才返回。如果调用pclose时它仍在运行,pclose将等待该进程的结束。
#include <stdio.h>
#include <errno.h>
int main()
{
char buf[100] = "awe";
#if 0
FILE *fp = popen("ls /home","r");
if(fp < 0)
{
perror("popen");
return -1;
}
fread(buf,sizeof(char),100,fp);
printf("buf is %s",bcuf);
#endif
FILE *fp = popen("cat > 1","w");
if(fp < 0)
{
perror("popen");
return -1;
}
fwrite(buf,sizeof(char),100,fp);
return 0;
}
3.管道类型
1.无名管道
#include <unistd.h>
int pipe(int filedes[2]);
当一个管道建立时,它会创建两个文件描述符:
filedis[0]用于读管道,filedis[1]用于写管道。
2.无名管道的读写
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
int main()
{
int pid;
int ret;
int pipefd[2];
char write_buf[50] = {0};
char read_buf[50] = {0};
ret = pipe(pipefd);
if(ret < 0)
{
perror("pipe");
return -1;
}
pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
close(pipefd[1]);//关闭写端
read(pipefd[0],read_buf,sizeof(read_buf));
printf("child pid is %d,read buf is %s\n",getpid(),read_buf);
close(pipefd[0]);
}
else if(pid > 0)
{
close(pipefd[0]);//关闭读端
//memcpy(write_buf,"123456",strlen("123456"));
printf("parent pid is %d\n",getpid());
scanf("%s",write_buf);
write(pipefd[1],write_buf,strlen(write_buf));
wait(NULL);
close(pipefd[1]);
}
return 0;
}
3.命名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname: FIFO文件名
mode:属性(同文件操作)
一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO。
name_pipe_send.c
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int ret;
int fd;
char buf[50];
ret = mkfifo("/home/namefifo",0666);
if(ret < 0)
{
perror("mkfifo");
return -1;
}
fd = open("/home/namefifo",O_WRONLY);
if(fd < 0)
{
perror("open");
return -1;
}
while(1)
{
printf("请输入:\n");
scanf("%s",buf);
write(fd,buf,strlen(buf));
memset(buf,0,sizeof(buf));
}
return 0;
}
name_pipe_read.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
char buf[50];
fd = open("/home/namefifo",O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
while(1)
{
read(fd,buf,sizeof(buf));
printf("收到的信息是: %s",buf);
memset(buf,0,sizeof(buf));
}
return 0;
}
4.管道关闭:关闭管道只需要将两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
二.共享内存
1.基本概念:
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
2. 实现:
- 创建共享内存,使用shmget函数
- 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
3.创建:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key:
1、0/IPC_PRIVATE:当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
2、大于0的32位整数:视参数shmflg来确定操作。
size:
1、大于0的整数:新建的共享内存大小,以字节为单位0:
2、只获取共享内存时指定为0
shmflg:模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定共享内存的存取权限
1、0:取共享内存标识符,若不存在则函数会报错
2、IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
3、IPC_CREAT|IPC_EXCL:如果内核中不存在键值 与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错
返回值:如果成功,返回共享内存标识符;如果失败,返回-1。
4.映射:
void* shmat(int shmid, char *shmaddr, int flag)
第一个参数,shm_id是由shmget函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
返回值:
如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1。
5.解除映射:
当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离
int shmdt(char *shmaddr);
参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。
6.共享内存控制:
shmid_ds结构至少包括以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数,shm_id是shmget函数返回的共享内存标识符。
第二个参数,command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
7.完整代码:
shm_read.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
int shmid;
char *ptr;
int ret;
shmid = shmget((key_t)0x1234,0,0666); //创建共享内存
if(shmid < 0)
{
perror("shmget");
return -1;
}
ptr = (char *)shmat(shmid,NULL,0); //映射
if((void *) -1 == ptr)
{
perror("shmat");
return -1;
}
ret = shmdt(ptr); //解除映射
if(ret < 0)
{
perror("shmdt");
return -1;
}
ret = shmctl(shmid,IPC_RMID,NULL); //共享内存控制
if(ret < 0)
{
perror("shmctl");
return -1;
}
printf("ptr is %s\n",ptr);
return 0;
}
shm_write.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main()
{
int shmid;
char *ptr;
shmid = shmget((key_t)0x1234,1024,IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
return -1;
}
ptr = (char *)shmat(shmid,NULL,0);
if((void *) -1 == ptr)
{
perror("shmat");
return -1;
}
memcpy(ptr,"adr",strlen("adr"));
return 0;
}
三.信号量
1.基本概念:
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行进程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。
2.工作原理:
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
3.创建:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。
第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1.
4.操作:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
第一个参数 sem_id是由semget返回的信号量标识符,
第二个参数 sembuf结构的定义如下:
struct sembuf
{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
第三个参数 nsops:信号操作结构的数量,恒大于或等于1
5.控制:
该函数用来直接控制信号量信息:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
第一个参数是信号量集IPC标识符。
第二个参数是操作信号在信号集中的编号,第一个信号的编号是0
第三个参数 command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
6.完整代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main()
{
int semid;
int ret;
struct sembuf p,v;
semid = semget((key_t)0x123,1,IPC_CREAT);
if(semid < 0)
{
perror("semget");
return -1;
}
p.sem_num = 0;
p.sem_op = -1;
p.sem_flg = SEM_UNDO;
v.sem_num = 0;
v.sem_op = 1;
v.sem_flg = SEM_UNDO;
while(1)
{
semop(semid,&p,1);
system("echo sem2 >> /test");
semop(semid,&v,1);
sleep(1);
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main()
{
int semid;
int ret;
struct sembuf p,v;
semid = semget((key_t)0x123,1,IPC_CREAT);
if(semid < 0)
{
perror("semget");
return -1;
}
ret = semctl(semid,0,SETVAL,1);
if(ret < 0)
{
perror("semctl");
return -1;
}
p.sem_num = 0;
p.sem_op = -1;
p.sem_flg = SEM_UNDO;
v.sem_num = 0;
v.sem_op = 1;
v.sem_flg = SEM_UNDO;
while(1)
{
semop(semid,&p,1);
system("echo sem1 >> /test");
semop(semid,&v,1);
sleep(1);
}
return 0;
}
四.消息队列
1.持续性:
系统V消息队列是随内核持续,只有在内核重启或者人工删除时,该消息队列才会被删除
2.键值:
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述符,必须提供该消息队列的键值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *pathname, char proj);
功能:返回文件名对应的键值。
pathname:文件名
proj:项目名(不为0即可)
3.打开/创建:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
key: 键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列的描述符。
msgflg取值:
IPC_CREAT
创建新的消息队列
IPC_EXCL
与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
IPC_NOWAIT
读写消息队列要求无法得到满足时,不阻塞。
在以下两种情况下,将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。
key参数为IPC_PRIVATE
int open_queue(key_t keyval)
{
int qid;
if ((qid = msgget(keyval, IPC_CREAT)) == -1)
{
return -1;
}
return qid;
}
4.发送消息:
int msgsnd(int msqid, struct msgbuf * msgp, int msgsz, int msgflg)
功能:向消息队列中发送一条消息
msqid:消息队列描述符
msgp:消息队列指针,指向存放消息的结构
msgsz:消息数据长度
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待
消息格式:
struct msgbuf
{
long mtype; // 消息类型 > 0
char mtext[1]; // 消息数据的首地址
}
5.接受消息:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf* msgp, int msgsz, long msgtp, int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功的读取了一条消息以后,队列中的这条消息将被删除。
6.队列控制:
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是将要采取的动作,它可以取3个值,
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功时返回0,失败时返回-1.
7.完整代码:
msg_send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype; //系统所规定的
char mbuf[50];
};
int main()
{
int msgid;
struct msgbuf buf;
msgid = msgget((key_t)0x1111,IPC_CREAT);
if(msgid < 0)
{
perror("msgget");
return -1;
}
memset(&buf,0,sizeof(buf));
buf.mtype = 88;
memcpy(buf.mbuf,"123",strlen("123"));
msgsnd(msgid,&buf,50,0);
return 0;
}
msg_recieve.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mbuf[50];
};
int main()
{
int msgid;
struct msgbuf buf;
msgid = msgget((key_t)0x1111,IPC_CREAT);
if(msgid < 0)
{
perror("msgget");
return -1;
}
memset(&buf,0,sizeof(buf));
msgrcv(msgid,&buf,50,88,0);
printf("buf = %s\n",buf.mbuf);
return 0;
}
五.信号通信
1.kill与kill -9
kill就是给某个进程id发送了一个信号。默认发送的信号是SIGTERM,而kill -9发送的信号是SIGKILL,即exit。exit信号不会被系统阻塞,所以kill -9能顺利杀掉进程.
2.signal
#include <signal.h>
typedef void (*sighandler_t)(int);
3.alarm
#include <unistd.h>;
unsigned int alarm(unsigned int seconds);
alarm()函数的主要功能是设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALARM的处理函数,那么alarm()默认处理终止进程。
函数返回值:如果在seconds秒内再次调用了alarm函数设置了新的闹钟,则后面定时器的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代;当参数seconds为0时,之前设置的定时器闹钟将被取消,并将剩下的时间返回。
4.sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
5.完整代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void func(int argc)
{
printf("argc = %d\n",argc);
}
void func1(int argc)
{
printf("alarm ### = %d\n",argc);
}
void func2(int argc,siginfo_t *info,void * argv)
{
printf("sig = %d\n",info->si_int);
}
int main()
{
//signal(SIGTERM,func);
struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = func2;
signal(SIGTERM,func1);
sigaction(SIGTERM,&act,NULL);
alarm(10);
while(1);
return 0;
}