=============================================================
系统编程技术点
1、进程的概念、接口函数
2、进程间的通信方式
3、进程间的信号集
4、线程的概念,函数接口,意义
5、线程之间的通信
6、线程池
execl簇函数
我们调用fork函数主要目的是调用外部程序功能,在进行外部程序调用时,一般有两种方法:一种是使用system函数,一种是execl簇函数
1、system 函数
#include <stdlib.h>
int system(const char *command);
command :需要调用的shell命令
2、exec函数族
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char arg, …
/ (char *) NULL */);
int execlp(const char *file, const char arg, …
/ (char *) NULL */);
int execle(const char *path, const char arg, …
/, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork failed");
return -1;
}
if(pid > 0)//父进程
{
//空操作
}
if(pid == 0)//子进程
{
system("./test hello world ");
//替换当前的子进程
//execl("./test", "hello", "world", NULL);
printf("child pid :%d\n", getpid());
}
return 0;
}
进程之间的通信
1, 无名管道
管道是linux系统中的一种特殊文件,数据从写端写入管道,并且只能从读端读出数据:让一个进程从管道的写端写入数据,另一个进程从读端读出数据,他们就实现一个单向通信。
缺点:无名管道可以实现不同进程之间的信息交换,但是只能工作于亲缘进程之间,不能实现任意两个不同进程之间的通信。
头文件:
#include <unistd.h>
函数:
int pipe(int pipefd[2]);
参数: 2 个成员的int型数组
在创建无名管道之后,返回两个文件描述符: 读端 :fd[0] , 写端:fd[1]
功能:创建一个无名管道,返回这个管道的读端和写端
注:在使用pipe函数时,父进程首先pipe创建无名管道,然后再创建子进程
例:使用无名管道实现父子进程之间的通信。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <strings.h>
int main()
{
int pipefd[2];
pid_t pid;
//1,创建无名管道
int ret = pipe(pipefd);
if(ret == -1)
{
perror("pipe failed");
return -1;
}
//2,创建子进程
pid = fork();
if(pid < 0)
{
perror("fork failed");
return -1;
}
if(pid > 0)
{
char buf[20]={
0};
while(1)
{
bzero(buf, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(pipefd[1], buf, sizeof(buf));
}
}
if(pid == 0)
{
while(1)
{
char buf[20] = {
0};
read(pipefd[0], buf, sizeof(buf));
printf("[%d] readbuf:%s\n", getpid(), buf);
}
}
return 0;
}
练习:使用无名管道,实现兄弟进程之间的通信。
即父进程创建两个子进程,两个子进程互相通信。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <strings.h>
int main()
{
int pipefd[2];
pid_t pid;
//1,创建无名管道
int ret = pipe(pipefd);
if(ret == -1)
{
perror("pipe failed");
return -1;
}
//2,创建子进程
pid = fork();
if(pid < 0)
{
perror("fork failed");
return -1;
}
if(pid > 0) //父进程执行部分
{
pid_t pid1;
pid1 = fork();
if(pid1 == 0) //子进程2执行内容
{
char buf[20]={
0};
while(1)
{
bzero(buf, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(pipefd[1], buf, sizeof(buf));
}
}
if(pid1 > 0)
{
wait(NULL);
wait(NULL);
}
}
if(pid == 0)//子进程1执行内容
{
char buf[20] = {
0};
while(1)
{
bzero(buf, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf("[%d] readbuf:%s\n", getpid(), buf);
}
}
return 0;
}
有名管道:FIFO (first in first out)
优点:能实现任意两个不同进程之间的通信。
有名管道实实在在的存在于文件系统中。
ls -l
- :普通文件
d :目录文件
p :管道文件
函数一:
int mkfifo(const char *pathname, mode_t mode);
头文件:
#include <sys/types.h>
#include <sys/stat.h>
参数:
pathname : 文件路径名(需要创建的管道文件)
mode : 文件权限 八进制 0777
返回值:成功返回0,失败返回-1;
函数二:
int access(const char *pathname, int mode);(判断文件是否具有某种功能)
头文件:#include <unistd.h>
参数:
pathname :路径名
mode :权限 F_OK :测试文件是否存在
返回值:
文件存在,返回0
文件不存在,返回 -1
练习:
使用access函数和mkfifo函数,在家目录下创建一个管道文件 fifo
如果文件不存在,则创建该文件,如果文件存在,则输出: file exist
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define FIFO_PATH "/home/gec/fifo"
int main()
{
//1,判断家目录下的fifo文件是否存在
int ret0 = access(FIFO_PATH, F_OK);
if(ret0 == 0)
{
printf("file exist\n");
}
else
{
int ret = mkfifo(FIFO_PATH, 0777);
if(ret == -1)
{
perror("mkfifo failed");
return -1;
}
}
return 0;
}
家目录: /home/gec 用户所独立拥有的一个目录 ~
(gec 为用户名)
例子:使用有名管道实现两个不同进程之间的数据通信。
需要同时打开两个终端同时运行程序,一个终端发送,另一个终端接收。
发送信息程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#define FIFO_PATH "/tmp/myfifo"
int main()
{
//1,判断文件是否存在,先创建一个管道文件
int ret0 = access(FIFO_PATH, F_OK);
if(ret0 == 0)
{
printf("file exist\n");
}
else
{
int ret = mkfifo(FIFO_PATH, 0777);
if(ret == -1)
{
perror("mkfifo failed");
return -1;
}
}
//2,打开管道文件 (读写权限打开)
int fd = open(FIFO_PATH, O_RDWR);
if(fd < 0)
{
perror("open fifo failed");
return -1;
}
//3,循环从键盘获取数据,写入管道文件
char buf[128];
while(1)
{
bzero(buf, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
write(fd, buf, sizeof(buf));
}
return 0;
}
接收信息程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#define FIFO_PATH "/tmp/myfifo"
int main()
{
//1,判断文件是否存在,先创建一个管道文件
int ret0 = access(FIFO_PATH, F_OK);
if(ret0 == 0)
{
printf("file exist\n");
}
else
{
int ret = mkfifo(FIFO_PATH, 0777);
if(ret == -1)
{
perror("mkfifo failed");
return -1;
}
}
//2,打开管道文件 (读写权限打开)
int fd = open(FIFO_PATH, O_RDWR);
if(fd < 0)
{
perror("open fifo failed");
return -1;
}
//3,循环从管道文件中读取数据,输出
char buf[128];
while(1)
{
bzero(buf, sizeof(buf));
read(fd, buf, sizeof(buf));
printf("recvbuf:%s", buf);
}
return 0;
}
思考:如何实现双向通信。
提示:使用两个管道
同样需要同时运行程序1和程序2。
程序1:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#include <sys/wait.h>
#define FIFO_PATH1 "/tmp/myfifo1"
#define FIFO_PATH "/tmp/myfifo"
int main()
{
//1,判断文件是否存在,先创建一个管道文件
int ret0 = access(FIFO_PATH, F_OK);
if(ret0 == 0)
{
printf("file exist\n");
}
else
{
int ret = mkfifo(FIFO_PATH, 0777);
if(ret == -1)
{
perror("mkfifo failed");
return -1;
}
}
int ret1 = access(FIFO_PATH1, F_OK);
if(ret1 == 0)
{
printf("file exist\n");
}
else
{
int ret1 = mkfifo(FIFO_PATH1, 0777);
if(ret1 == -1)
{
perror("mkfifo1 failed");
return -1;
}
}
//2,打开管道文件 (读写权限打开)
int fd = open(FIFO_PATH, O_RDWR);
int fd1 = open(FIFO_PATH1, O_RDWR);
if(fd < 0 || fd1 < 0)
{
perror("open fifo failed");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid < 0){
perror("fork failed \n");
return -1;
}
if(pid > 0){
//3,循环从管道文件中写入数据
char buf[128];
while(1)
{
bzero(buf, sizeof(buf));
fgets(buf,sizeof(buf),stdin);
write(fd, buf, sizeof(buf)); //阻塞
printf("Terminal 2 sendinfo:%s", buf);
}
wait(NULL);
}
if(pid == 0){
//3,循环从管道文件中读取数据
char buf[128];
while(1)
{
bzero(buf, sizeof(buf));
read(fd1, buf, sizeof(buf)); //阻塞
printf("Terminal 2 recvinfo:%s", buf);
}
}
return 0;
}
程序2:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#include <sys/wait.h>
#define FIFO_PATH1 "/tmp/myfifo1"
#define FIFO_PATH "/tmp/myfifo"
int main()
{
//1,判断文件是否存在,先创建一个管道文件
int ret0 = access(FIFO_PATH, F_OK);
if(ret0 == 0)
{
printf("file exist\n");
}
else
{
int ret = mkfifo(FIFO_PATH, 0777);
if(ret == -1)
{
perror("mkfifo failed");
return -1;
}
}
int ret1 = access(FIFO_PATH1, F_OK);
if(ret1 == 0)
{
printf("file exist\n");
}
else
{
int ret1 = mkfifo(FIFO_PATH1, 0777);
if(ret1 == -1)
{
perror("mkfifo1 failed");
return -1;
}
}
//2,打开管道文件 (读写权限打开)
int fd = open(FIFO_PATH, O_RDWR);
int fd1 = open(FIFO_PATH1, O_RDWR);
if(fd < 0 || fd1 < 0)
{
perror("open fifo failed");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid < 0){
perror("fork failed \n");
return -1;
}
if(pid > 0){
//3,循环从管道文件中读取数据,输出
char buf[128];
while(1)
{
bzero(buf, sizeof(buf));
read(fd, buf, sizeof(buf)); //阻塞
printf("Terminal 1 recvinfo:%s", buf);
}
wait(NULL);
}
if(pid == 0){
char buf[128];
while(1)
{
//3,循环从管道文件中写入数据
bzero(buf, sizeof(buf));
fgets(buf,sizeof(buf),stdin);
write(fd1, buf, sizeof(buf)); //阻塞
printf("Terminal 1 sendinfo:%s", buf);
}
}
return 0;
}
有名管道的特点:
1) 有名管道是以文件形式存在于操作系统中,可以通过文件IO访问管道
2) 管道文件的读端和写端必须要同时存在
3) 有名管道可以实现单方向的数据通信:数据通信可以是1对1,多对一的形式
4) 数据写入具有原子性 (数据写入要么成功,要么失败)
信号
1、信号有哪些?
2、如何发出信号?
(1)一些特定的操作会发送信号给进程 。例如 ctrl + c 发送 SIGINT
(2)使用 kill 命令进行发送
格式: kill -信号值 进程ID 例如: kill -2 7289 / kill -SIGINT 7289
格式: killall -信号值 进程名字 例如: killall -9 ./double_fifo1
(3)在程序中使用kill函数发送信号
函数一:
int kill(pid_t pid, int sig);
头文件:
#include <sys/types.h>
#include <signal.h>
参数:
pid : 进程ID
sig : 信号值
例子:创建一个子进程,子进程每隔1s输出hello, 父进程5S后发送信号杀死子进程
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork failed");
return -1;
}
if(pid > 0)
{
while(1)
{
printf("hello\n");
sleep(1);
}
}
if(pid == 0)
{
pid_t pid1 = getppid();
sleep(5);
kill(pid1, 9);
}
return 0;
}
函数二:
typedef void (*sighandler_t)(int); //函数指针
sighandler_t signal(int signum, sighandler_t handler);
头文件:#include <signal.h>
参数:
signum : 要捕捉的信号 ( 9) SIGKILL 19) SIGSTOP不能捕捉)
handler : 注册执行函数 (接收到捕捉信号之后进程执行的函数)
例子:设置一个进程,捕捉信号SIGINT, 接收到信号之后,输出hello
#include <stdio.h>
#include <signal.h>
void func(int arg)
{
printf("hello\n");
}
int main()
{
//1,注册捕捉信号SIGINT,捕捉到SIGINT信号后运行func()函数
signal(SIGINT, func);
while(1);
return 0;
}
其余函数:
#include <signal.h>
int raise(int sig);
sig : 发送的信号值
#include <unistd.h>
int pause(void); //挂起当前进程 (进程睡眠)等待外部发送一个信号