您的位置:首页 > 运维架构 > Linux

进程控制理论<二>---那些年我们一起学习linux程序设计

2012-06-20 16:46 621 查看
进程通信

目的:

为什么进程间需要通信?

(1) 数据传输

一个进程需要将它的数据发送给另一个进程

(2) 资源共享

多个进程之间共享同样的资源

(3) 通知事件

一个进程需要向另一个或一组进程发送消息,通知他们发生了某事件

(4) 进程控制

有些进程希望完全控制另一个进程的执行(如:debug进程)

此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。

Linux 进程间通信(IPC)由以下几部分发展而来:

UNIX 进程间通信
基于System V进程间通信
POSIX进程间通信

POSIX 介绍

POSIX(portable operating system interface)

表示可移植操作系统接口。电气和电子工程师协会(institute of Electrical and Electronics Engineers ,IEEE)最初开发POSIX标准是为了提高UNIX环境下应用程序的可移植性。然而,POSIX并不局限与UNIX,许多其他的操作系统,例如:DEC
OpenVMS和MicrosoftWindows,都支持POSIX标准。


System V 分类:(必须记住)

现在Linux 使用的进程间通信方式包括:

(1) 管道(pipe)和有名管道(FIFO)(信息传输UNIX进程间通信

(2) 信号(signal)(控制作用)UNIX进程间通信

(3) 消息队列 system v进程间通信

(4) 共享内存 system v进程间通信

(5) 信号量 system v进程间通信

(6) 套接字(socket)

关(管)心(信)小(消息队列)攻(共享内存)心(信号量)疼(套)

管道通信

什么是管道?

管道是单向的、先进先出的,它把一个进程的输入和另一个进程的输出连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。

数据被一个进程读出后,将被从管道中删除,其他读进程将不能再读到这些数据。

管道提供了简单的流程控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。

一.管道创建:

管道包括无名管道和有名管道两种,前者用于父进程和子进程间通信,后者可用于运行于同一系统中的任意两个进程间的他通信。

无名管道由pipe()函数创建:

int pipe (int filedis[2]);

返回值:若成功则返回0;否则返回-1;错误原因从存于error中

当一个管道建立时,它会创建两个文件描述符:filedis[0]用于读管道(管道头部),filedis[1]用于写管道(管道尾部)。

二.管道关闭:

关闭管道只需将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。

例程:

#include <unistd.h>

#include <error.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

int pipe_fd[2];

if (pipe(pipe_fd)<0) //(pipe(pipe_fd)已经是在创建管道

{

printf(“pipe create error\n”);

return -1;

}

else

printf(“pipe creat success \n”);

close(pipe_fd[0]);

close(pipe_fd[1]);

}

三.管道读写

管道用于不同进程间通信。通常先创建一个管道,在通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。(这样子进程会对父进程与管道相关的信息或者是堆、栈进程拷贝走,让子进程也知道管道在哪)

注意事项:

必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。

因为:在fork之后创建按管道,即是在父进程中创建管道,子进程不会知道;但如果在fork之前创建管道,子进程会继承父进程所创建的管道。

综合例程:

#include <unistd.h>

#include <sys/types.h>

#include <errno.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

int pipe_fd[2];

pid_t pid;

char buf_r[100];

char* p_wbuf;

int r_num;

memset(buf_r,0,sizeof(buf_r));

/*创建管道*/

if(pipe(pipe_fd)<0)

{

printf("pipe create error\n");

return -1;

}

/*创建子进程*/

if((pid=fork())==0)  //子进程 OR父进程?

{

printf("\n");

close(pipe_fd[1]);

sleep(2); /*为什么要睡眠原因:让父进程先往管道写东西,2秒后子进程再读管道*/

if((r_num=read(pipe_fd[0],buf_r,100))>0)

{

printf( "%d numbers read from the pipe is %s\n",r_num,buf_r);

}

close(pipe_fd[0]);

exit(0);

}

else if(pid>0)

{

close(pipe_fd[0]);

if(write(pipe_fd[1],"Hello",5)!=-1)

printf("parent write1 Hello!\n");

if(write(pipe_fd[1]," Pipe",5)!=-1)

printf("parent write2 Pipe!\n");

close(pipe_fd[1]);

sleep(3);

waitpid(pid,NULL,0); /*waitpid等待指定的子进程结束wait等待某个子进程结束*/

exit(0);

}

return 0;

}

四.命名管道(FIFO)

命名管道和无名管道基本相同,有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。

创建有名管道:

#include<sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *pathname ,mode_t mode)

(1) pathname :FIF
文件名(管道名)

(2) mode : 属性

一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、
write等)都可用于FIFO。


操作:

当打开FIFO时,非阻塞标志(O_NONBLOCK)将对以后的读写产生如下影响:

1. 没有使用O_NONBLOCK:访问要求无法满足时将阻塞。如试图读取空的FIFO,将导致进程阻塞。

2. 使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO

例程分析:

fifo_write.c

fifo_read.c

fifo_write.c

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define FIFO_SERVER "/tmp/myfifo"

main(int argc,char** argv)

{

int fd;

char w_buf[100];

int nwrite;

/*打开管道*/

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);/**打开的管道名必须和在fifo_read.c创建的管道为同一管道/

if(argc==1)

{

printf("Please send something\n");

exit(-1);

}

strcpy(w_buf,argv[1]);

/*向管道写入数据 */

if((nwrite=write(fd,w_buf,100))==-1)

{

if(errno==EAGAIN)

printf("The FIFO has not been read yet.Please try later\n");

}

else

printf("write %s to the FIFO\n",w_buf);

}

fifo_read.c

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define FIFO "/tmp/myfifo"

main(int argc,char** argv)

{

char buf_r[100];

int  fd;

int  nread;

/*创建管道 */

if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

printf("cannot create fifoserver\n");

printf("Preparing for reading bytes...\n");

memset(buf_r,0,sizeof(buf_r));

/*打开管道 */

fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);

if(fd==-1)

{

perror("open");

exit(1);

}

while(1)

{

memset(buf_r,0,sizeof(buf_r));

if((nread=read(fd,buf_r,100))==-1)

{

if(errno==EAGAIN)

printf("no data yet\n");

}

printf("read %s from FIFO\n",buf_r);

sleep(1);

}

pause(); /*暂停,等待信号*/

unlink(FIFO); //删除文件

}

信号通信:

一.信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:

1. 当用户按某些按键时,产生信号。

2. 硬件异常产生信号:除数为0、无效的存储访问等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程产生一个SIGSEGC信号。

3. 进程用kill函数将信号发送给另一个进程;

4. 用户可用kill命令将信号发送给其它进程。

二.信号类型:



三.信号处理:

当某信号出现时,将按照下列三种方式中的一种进行处理:

(1) 忽略此信号

大多数信号都是按照这种方式进行处理的,但是有两种信号却决不能被忽略。它们是:SIGKILL和 SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法。

(2) 执行用户希望的动作

通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。

(3) 执行系统默认动作

对大多数信号的系统默认动作是终止该进程。

四.信号发送:

(1)发送信号的主要函数有 kill 和raise

区别:

用Kill -l 可以看到编号(前面的数字)

Kill 既可以向自身发送信号,也可以向其他进程发送信号。与kill函数不同的是,raise函数是向进程自身发送信号。

#include <sys/types.h>

#include <signal.h>

int kill (pid_t pid ,int signo) //第一个参数:发给哪一个进程,这个进程的pid是多少;第二个参数:发送什么信号,信号编号是多少

int raise (int signo) //因为只给自己发,所以只需要信号编号

kill 的pid参数有四种不同的取值情况:

1. pid>0

将信号发送给进程ID为pid的进程;

2.pid==0

将信号发送给同组的进程;

3.pid<0

将信号发送给其进程组ID等于pid绝对值的进程;

4.pid==-1

将信号发送给所有进程

(2)Alarm

使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。(这个信号是发给自己的)

#include <unistd.h>

Unsigned int alarm (unsigned int seconds)

(1)Seconds:经过了指定的seconds秒后会产生信号SIGALRM.

(2)每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它没有超时,以前登记的闹钟时间则被新值代替。

(3)如果有以前登记的尚未超过的闹钟时间,而这次seconds值为0.则表示取消以前的闹钟。

(3)Pause

Pause函数使用进程挂起直至捕捉到一个信号。

#include <unistd.h>

int pause (void)

pause让进程一直等待;pause结束等待的条件,进程收到一个信号

信号的处理:

信号处理的主要方法有两种:一种:是使用简单的SIGNAL函数,


另一种:是使用信号集函数组。


signal

#include<signal.h>

void(*signal (int signal,void(*func)(int)))(int)

func 可能的值是:

1.SIG_IGN:忽略此信号;

2.SIG_DFL:按系统默认方式处理

3.信号处理函数名:使用该函数处理

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

void my_func(int sign_no) /*所有信号处理函数,只有一个参数,且必须是整型*/

{

if(sign_no==SIGINT)

printf("I have get SIGINT\n");

else if(sign_no==SIGQUIT)

printf("I have get SIGQUIT\n");

}

int main()

{

printf("Waiting for signal SIGINT or SIGQUIT \n ");

/*注册信号处理函数*/

signal(SIGINT, my_func);

signal(SIGQUIT, my_func);

pause();/*等待。。。直到进程收到一个信号*/

exit(0);

}

搜索进程号指令: ps aux

Kill -s SIGQUIT 3687 (给进程为号3687的进程发送一个SIGQUIT指令)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: