一、进程的基本概念
进程是指一个具有独立功能的程序在某个数据集合上的一次动态执行过程,他是操作系统进行资源分配的最小单元(线程是操作系统进行调度的最小单元)。进程具有并发性、动态性、交互性和独立性等主要特性
从操作系统的角度看,进程是程序执行时相关资源的总成。当进程结束时,所有资源被操作系统回收
二、进程间通信
由于在操作系统中进程具有独立性,所以为了实现进程之间通信,linux操作系统中提供了多种通信方式
接下来对各种通信方式进行说明,并用实例进行演示
无名管道与有名管道
1、 无名管道
无名管道有如下特点:
(1)无名管道的创建函数
所需头文件 | #include |
函数原型 | int pipe(int pipefd[2]); |
函数传入值 | fd:包含两个元素的整型数组,存放管道对应的文件描述符 |
函数返回值 | 成功:返回0 |
失败:返回-1 |
当一个管道建立时,它会创建两个文件描述符:fd[0]、fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
(2)管道读写说明
用pipe()函数创建的管道两端处于一个进程中。由于管道主要时用于不同进程间的通信,通常先是创建一个管道,再调用fork()函数创建一个子进程,该子进程会继承父进程所创建的管道。需要注意的是,无名管道是单工的通信方式,即进程要么只能读管道要么只能写管道。父子进程虽然都拥有管道的读端和写端,但是只能使用其中一个,这样就应该把不使用的读端或写端文件描述符关闭。例如将父进程的写端fd[1]和子进程的读端fd[0]关闭,此时父子进程之间就建立了一条“子进程写入父进程读取”的通道。同样反过来也可以。
(3)实例演示:无名管道实现父进程写子进程读
#include
#include
#include
#include
#include
#define MAX_DATEA_LEN 100
int main(int argc, char *argv[])
{
pid_t pid;
int pipe_fd[2];
const char date[] = "Pipe test programe";
char buf[MAX_DATEA_LEN] = {0};
int real_read,real_write;
if(pipe(pipe_fd) == -1)
{
perror("fail to pipe");
exit(-1);
}
if((pid = fork()) == -1)
{
perror("fail to fork");
exit(-1);
}
else if(pid == 0)
{
/*子进程关闭写文件描述符,并通过1s等待父进程已关闭相应的读文件描述符*/
close (pipe_fd[1]);
sleep(1);
if(real_read = read(pipe_fd[0], buf, MAX_DATEA_LEN));
{
printf("% date bytes read from the pipe is '%s'\n", real_read, buf);
}
close(pipe_fd[0]);
exit (0);
}
else if(pid > 0)
{
/*关闭读文件描述符*/
close (pipe_fd[0]);
if((real_write = write(pipe_fd[1], date, strlen(date))) != -1)
{
printf("parent wrote %d byte : '%s'\n", real_write, date);
}
close(pipe_fd[1]);
waitpid(pid, NULL, 0);
exit (0);
}
}
运行结果如下:
2、有名管道
有名管道有如下特点:
有名管道的创建使用mkfifo()函数,该函数类似于文件中的open()操作,可以指定管道的路径与访问权限(用户也可以在命令行使用“mknod <管道名>”来创建有名管道)。在创建成功之后就可以使用open()、read()、write()这些函数了。与普通文件一样,对于为读二打开的管道可在open()中设置O_RDONLY,对于为写而打开的管道可在open()中设置O_WEONLY。
缺省情况下,如果当前fifo中没有数据,读进程将一直阻塞到有数据写入或者是fifo写端都关闭
(1)有名管道的创建函数mkfio()
所需头文件 | #include #include |
函数原型 | int mkfifo(const char *pathname, mode_t mode); |
函数传入值 | pathname:要创建的管道名 |
mode;管道的访问权限 | |
函数返回值 | 成功:返回0 |
失败:返回-1 |
(2)实例演示:该演示实例包括两个程序,一个用于读管道,另一个用于写管道。其中在读管道的程序里创建管道,并且作为main函数里的参数由用户输入要写入的内容。读管道的程序会读出用户写入管道的内容
/*
fifo_write.c
*/
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
int num_write;
if(argc < 2)
{
printf("Usage: ./fifo_write string\n");
exit(-1);
}
if((fd = open("MYFIFO", O_WRONLY)) < 0)
{
perror("fail to open fifo");
exit(-1);
}
if((num_write = write(fd, argv[1], strlen(argv[1])+1)) > 0)
{
printf("Write '%s' to FIFO\n", argv[1]);
}
close(fd);
return 0;
}
/*
fifo_write.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
char buf[256];
int num_read = 0;
/*判断有名管道是否存在,若没有创建,则以相应的权限创建*/
if(access("MYFIFO", F_OK) == -1)
{
if(mkfifo("MYFIFO", 0666) == -1)
{
perror("fail to mkfifo");
exit(-1);
}
}
/*以只读的方式打开有名管道*/
if((fd = open("MYFIFO", O_RDONLY)) == -1)
{
perror("fail to open");
exit(-1);
}
while(1)
{
memset(buf, 0, sizeof(buf));
if((num_read = read(fd, buf, sizeof(buf))) > 0)
{
printf("Read '%s' from FIFO\n", buf);
}
}
close(fd);
return 0;
}
运行结果如下:
3、信号
(1)信号概述:
信号是在软件层次上对中断机制的一种模拟。信号是异步的:一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。他可以在任何时候发给某一-进程,而无需知道该进程的状态,如果当前并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
信号是进程间通信机制中唯一的异步通信机制。我们可以把这种机制看作异步通知,通知接收信号的进程有哪些事情发生了,他除了基本的通知功能外,还可以传递附加消息。 信号的产生有硬件来源和软件来源,常用的信号相关的函数会有kill()、raise()、alarm()、setitimer()和sigqueue()等,进程可以通过三种方式来相应信号,忽略信号、捕捉信号、执行默认操作
发送信号的函数:kill()、raise()
设置信号函数:signal()、sigaction()
其他函数:alarm()、pause()
(2)信号的发送
kil()和raise()函数是信号发送函数,kill()函数可以向指定的进程发送信号,但是raise()函数只允许向自身发送信号
kill()函数语法要点:
所需头文件 | #include #include |
|
函数原型 | int kill(pid_t pid, int sig); | |
函数传入值 | pid | 正数:发送信号给进成为pid的进程 |
0:信号被发送到所有和当前进程在同一进程组的进程 | ||
-1:信号发送给所有的进程表中的进程(除了进程号最大的进程外) | ||
< -1:信号发送给进程组号为-pid的每一个进程 | ||
函数返回值 | 成功:0 | |
错误:-1 |
raise()函数语法要点:
所需头文件 | #include #include |
函数原型 | int raise(int sig); |
函数传入值 | sig:信号类型 |
函数返回值 | 成功:0 |
错误:-1 |
(3)实例演示:
下面实例使用fork()函数创建了一个子进程,在子进程中使用raise()函数向自身发送SIGSTOP信号,使子进程暂停;接下来再在父进程中调用kill()函数向子进程发送信号,使用的是SIGKILL。
/*
kill_raise.c
*/
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fork error");
exit(-1);
}
if(pid == 0)
{
/*在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停。raise()函数只允许向自身发送信号*/
printf("child(pid:%d) is waiting for any signal\n", getpid());
raise(SIGSTOP);
}
else
{
/*在父进程中收集子进程的状态,并调用kill*()函数发送信号*/
sleep(1);
if((waitpid(pid, NULL, WNOHANG)) == 0)
{
kill(pid, SIGKILL);
printf("parent kill child process %d\n", pid);
}
waitpid(pid, NULL, 0);
exit(0);
}
}
运行结果如下:
(4)定时器信号:alarm()、pause()
alarm()也成为闹钟函数,他可以在进程中设置一个定时器。当定时器指定的时间到时,它就向进程发送SIGALARM信号。要注意的是,一个进程只有一个闹钟时间,如果在调用alarm()函数之前设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。pause()函数是用于将调用进程挂起直至接收到信号为止。
alarm()函数语法要点
所需头文件 | #include |
函数原型 | unsigned int alarm(unsigned int seconds); |
函数传入值 | seconds:指定秒数,系统经过seconds秒之后向进程发送SIGALARM信号 |
函数返回值 | 成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟剩余的时间。如果是第一次设置则返回0 |
出错:-1 |
pasue()函数的语法要点
所需头文件 | #include |
函数原型 | int pause(void); |
函数传入值 | 空 |
函数返回值 | -1,并且把errno值设置为EINT |
#include
#include
#include
int main(int argc, char *argv[])
{
/*调用定时器alarm定时器函数*/
alarm(5);
pause();
printf("I have been wake up \n"); //这条语句不会被执行
}
(5)信号的设置:signal()和sigaction()
有关signal()函数前面已经介绍过
参考:https://blog.csdn.net/David_361/article/details/86569985
这里主要对sigaction()函数做一下补充
sigaction()函数相对signal()函数更加健壮、功能更全。sigaction可以一次得到设置新捕获函数和获取旧的捕获函数(其实还可以单独设置新的捕获或者单独只获取旧的捕获函数),而signal函数不能单独获取旧的捕获函数而必须在设置新的捕获函数的同时才获取旧的捕获函数。
所需头文件 | #include |
函数原型 | int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); |
函数传入值 | signum:信号类型,除SIGKILL及SIGSTOP之外的任何一个信号 |
act:指向sigaction结构体的指针,包含对指定信号的处理 | |
oldact:保存信号原先的处理方式 | |
函数返回值 | 成功:0 |
错误:-1 |
下面看一下struct sigsction结构体
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
实例演示 ,分别用signal()和sigaction()函数实现捕捉信号,并进行给定的处理
/*
signal.c
*/
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no == SIGINT)
{
printf("I have got SIGINT\n");
}
else if(sign_no == SIGQUIT)
{
printf("I have got SIGQUIT\n");
}
}
int main(int argc, char *argv[])
{
printf("waiting for signal ...\n");
/*设置信号处理函数*/
signal(SIGINT, my_func);
signal(SIGQUIT,my_func);
pause();
exit(0);
}
/*
sigaction.c
*/
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no == SIGINT)
{
printf("I have got SIGINT\n");
}
else if(sign_no == SIGQUIT)
{
printf("I have got SIGQUIT\n");
}
}
int main(int argc, char *argv[])
{
struct sigaction action;
/*sigaction结构体的初始化*/
sigaction(SIGINT, 0, &action);
action.sa_handler = my_func;
sigaction(SIGINT, &action, 0);
sigaction(SIGQUIT, 0, &action);
action.sa_handler = my_func;
sigaction(SIGQUIT, &action, 0);
printf("waiting for signal ...\n");
pause();
exit(0);
}
实验结果如图:
4、信号量
5、共享内存
6、消息队列
进程间通信下:https://blog.csdn.net/David_361/article/details/86605455