有人问起,我就写一篇吧,不然最近也不知道有什么东西好写的.要说进程间通信,首先要明白进程是什么,进程都有什么状态,进程... (说不完了.)
我就先盗图一下.Linux的几大状态.

可以看到到大部分程序都在S状态.而我刚才是通过执行ps -aux来查询的,这句命令在我查询过程中就是R状态,所以他就显示成R状态了.

另外,进程都有PID,可以说是他们的号码牌,通过下面的程序可以获得自己的PID.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("current process pid = %d
", getpid());
fflush(stdout); /* <============== Put a breakpoint here */
return 0;
}
执行效果:

检查下对不对:

通过pstree我们可知道这个程序有父进程的,所以也可以获取他的父进程ID.

代码片段:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("current process pid = %d
", getpid());
printf("parent pid = %d
", getppid());
fflush(stdout); /* <============== Put a breakpoint here */
return 0;
}
执行效果:

用ps再检查下.果然是用gdb启动的.(因为我们在调试,所以从GDB启动的.)

有了上面基础,我们就可以继续接受下面知识了,有一个函数叫fork,就是主程序把自己复刻一份.我们测试代码片:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork(); /* 创建进程 */
if (pid == 0) /* 对子进程返回 0 */
{
printf("Here is child, my pid = %d, parent's pid = %d
", getpid(), getppid()); /* 打印父子进程 PID */
exit(0);
}
else if (pid > 0) /*对父进程返回子进程 PID */
{
printf("Here is parent, my pid = %d, child's pid = %d
", getpid(), pid);
}
else
{
perror("fork error
"); /* fork 出错 */
}
return 0;
}
注意,这份代码被执行了两次,包括fork程序.第一次执行的是父程序,他执行fork后,返回值是儿子的pid,第二次是儿子执行fork,他本来就是儿子了,返回0.fork是不会返回负数的,除非出错了.执行效果:

继续用ps aux检查,果然两个进程了.

但是现在执行的两份程序都是在一个程序做完的.并且父子一起结束了.有没有办法可以exec外面程序呢?当然可以啦.比如我执行ls [注意路径]
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork(); /* 创建进程 */
if (pid == 0) /* 对子进程返回 0 */
{
execl("/bin/ps", "ps","-alh",NULL);
perror("execle error");
exit(0);
}
else if (pid > 0) /*对父进程返回子进程 PID */
{
exit(0);
}
else
{
perror("fork error
"); /* fork 出错 */
}
return 0;
}
注意,要在父退出前下断点,否则就子没法做了.现在ps了一下进程列表.

exec有很多的变形.
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg,..., 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[]);
大概总结下:
- 后缀 p表示使用 filename 做参数,如果 filename 中包含/则视其为路径名,否则将会在 PATH 环境变量所指定的各个目录中搜索该文件,如 execlp()函数.无后缀 p 则必须使用路径名来指定可执行文件的位置,比如execl()函数.
- 后缀 e表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以 NULL 结束,如 execvpe()函数.而无此后缀的函数则将当前进程的 environ 变量复制给新的程序,如
execv()函数. - 后缀 l表示使用 list 形式来传递新程序的参数,所有这些参数均以可变参数的形式在exec 中给出,最后一个参数需要是 NULL 用以表示没有更多的参数了,如 execl()函数.
- 后缀 v 表示使用 vector 形式来传递新程序的参数,传给新程序的所有参数放入一个字符串数组中,数组以 NULL 结束以表示结尾,如 execv()函数.
刚才说了,不要让父先退出,如果父真的要先退出,那么子程序就是孤儿进程,如果是儿子先退出,那么就是僵尸进程,不管哪个都不好,孤儿进程让系统init程序来收集,僵尸进程就占着坑不会放.当然,父进程也退出了,那么僵尸也会变成孤儿.
正常代码做法:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wait.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
int exit_code;
pid_t pid;
pid = fork(); /* 创建进程 */
if (pid == 0) /* 对子进程返回 0 */
{
exit(0);
}
else if (pid > 0) /*对父进程返回子进程 PID */
{
if (wait(&exit_code) != pid)
{
exit(1);/* 正常来说父进程在等子进程,等到wait才返回,所以wait返回后判断pid,我等的是不是这个儿子啊. */
}
else
{
printf("child exit code = %d
", exit_code);
exit(0);
}
}
else
{
perror("fork error
"); /* fork 出错 */
}
return 0;
}
执行效果:

我fork了之后,父亲直接退出会丢init管理,也是一个实现daemon的方法,但是不规范.不过daemon这个声明需要人工声明下.(只需要声明.)
两个参数:
int daemon(int nochdir, int noclose);
nochdir如果为0,那么改变执行目录到根目录,否则不改,noclose如果为0,那么不输出东西到屏幕.一般都传两个参数为0就够用了.函数成功返回0,否则-1.一旦执行,这个程序就完成使命了.先复习下我们之前的CPU温度记录软件.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
int main(int argc, char *argv [])
{
int cpu_freq_fd = -1;
int cpu_temp_fd = -1;
int cpu_logger_fd = -1;
char freq_buf[128] = {0x00};
char temp_buf[128] = {0x00};
char log_buf[100] = {0x00};
time_t rawtime;
struct tm * timeinfo;
cpu_freq_fd = open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", O_RDONLY);
cpu_temp_fd = open("/sys/class/thermal/thermal_zone0/temp", O_RDONLY);
cpu_logger_fd = open("/tmp/cpu.log", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
while (1)
{
memset(log_buf, 0x20, sizeof(log_buf));
lseek(cpu_freq_fd, 0, SEEK_SET); /* 回到文件的开始 */
read(cpu_freq_fd, freq_buf, sizeof(freq_buf)); /* 读取文件 */
lseek(cpu_temp_fd, 0, SEEK_SET);
read(cpu_temp_fd, temp_buf, sizeof(temp_buf));
time(&rawtime);
timeinfo = localtime(&rawtime);
char *now_time = asctime(timeinfo);
now_time[strlen(now_time) - 1] = 0;
sprintf(log_buf,"%s - CPU = %d MHz - Temp = %d degC
", now_time, atoi(freq_buf) / 1000, atoi(temp_buf) / 1000);
write(cpu_logger_fd, log_buf, strlen(log_buf));
fsync(cpu_logger_fd);
sleep(1);
}
close(cpu_freq_fd);
close(cpu_temp_fd);
close(cpu_logger_fd);
return 0;
}
通过daemon的话,改成:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wait.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
int daemon(int nochdir, int noclose);
int main(void)
{
int cpu_freq_fd = -1;
int cpu_temp_fd = -1;
int cpu_logger_fd = -1;
char freq_buf[128] = { 0x00 };
char temp_buf[128] = { 0x00 };
char log_buf[100] = { 0x00 };
time_t rawtime;
struct tm * timeinfo;
if (daemon(0, 0) == -1) {
perror("daemon error");
exit(-1);
}
cpu_freq_fd = open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", O_RDONLY);
cpu_temp_fd = open("/sys/class/thermal/thermal_zone0/temp", O_RDONLY);
cpu_logger_fd = open("/tmp/cpu.log", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
while (1)
{
memset(log_buf, 0x20, sizeof(log_buf));
lseek(cpu_freq_fd, 0, SEEK_SET); /* 回到文件的开始 */
read(cpu_freq_fd, freq_buf, sizeof(freq_buf)); /* 读取文件 */
lseek(cpu_temp_fd, 0, SEEK_SET);
read(cpu_temp_fd, temp_buf, sizeof(temp_buf));
time(&rawtime);
timeinfo = localtime(&rawtime);
char *now_time = asctime(timeinfo);
now_time[strlen(now_time) - 1] = 0;
sprintf(log_buf, "%s - CPU = %d MHz - Temp = %d degC
", now_time, atoi(freq_buf) / 1000, atoi(temp_buf) / 1000);
write(cpu_logger_fd, log_buf, strlen(log_buf));
fsync(cpu_logger_fd);
sleep(1);
}
close(cpu_freq_fd);
close(cpu_temp_fd);
close(cpu_logger_fd);
return 0;
return 0;
}
这次,执行立马就会返回,我们把它编译出来,然后复制出来,重启Linux,保证所有关于开发环境的东西都没了.比如我的执行程序叫MainFork.

执行后可见程序立马返回了.但是在后台勤勤恳恳执行中了.

通过ps查看,返回不代表结束啊.

说了辣么多,对进程肯定有不少了解了,开始了解管道....这就是最终的通信方式.管道两种,一种命名管道,一种匿名管道.之前有人问我能不能创建一个TXT文件,然后两个C一起通过TXT来交换数据,当然是可以的,这就是命名管道原理,不过命名管道用的是RAM而已.在说命名管道之前,说说匿名管道,匿名管道只能在当前程序内通信... 所以肯定没命名管道广泛了.(其实也不绝对,比如某些服务器软件大量匿名管道.)
下面程序使用的是匿名管道,让子进程打印数据,父进程就是个push的作用而已.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wait.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2)
{
fprintf(stderr, "Usage: %s <string>
", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1) /* 创建匿名管道 */
{
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork(); /* 创建子进程 */
if (cpid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) /* 子进程读管道读端 */
{
close(pipefd[1]); /* 关闭不需要的写端 */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "
", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
}
else /* 父进程写 argv[1]到管道 */
{
close(pipefd[0]); /* 关闭不需要的读端 */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* 关闭文件发送 EOF,子进程停止读*/
wait(NULL); /* 等待子进程退出 */
exit(EXIT_SUCCESS);
}
}
效果:

下面命名管道,这就是两个程序了,首先程序1功能是读取一个img文件(其实Linux下没扩展名概念,方便理解这么说了.),把文件put到管道里面,管道容量比较小,1K而已.
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 1024 /* 一次最大写 1024 个字节 */
int main(int argc, char *argv[])
{
const char *fifoname = "/tmp/fifo-tater"; /* 命名管道文件名 */
int pipefd, datafd;
int bytes, ret;
char buffer[BUFSIZE];
if (argc != 2) /* 带文件名参数 */
{
fprintf(stderr, "Usage: %s < filename >
", argv[0]);
exit(EXIT_FAILURE);
}
if (access(fifoname, F_OK) < 0) /* 判断文件是否已存在 */
{
ret = mkfifo(fifoname, 0777); /* 创建管道文件 */
if (ret < 0)
{
perror("mkfifo error");
exit(EXIT_FAILURE);
}
}
pipefd = open(fifoname, O_WRONLY); /* 打开管道文件 */
datafd = open(argv[1], O_RDONLY); /* 打开数据文件 */
if ((pipefd > 0) && (datafd > 0)) /* 将数据文件读出并写到管道文件 */
{
bytes = read(datafd, buffer, BUFSIZE);
while (bytes > 0)
{
ret = write(pipefd, buffer, bytes);
if (ret < 0)
{
perror("write error");
exit(EXIT_FAILURE);
}
bytes = read(datafd, buffer, BUFSIZE);
}
close(pipefd);
close(datafd);
}
else
{
exit(EXIT_FAILURE);
}
return 0;
}
对应的接受程序:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 1024
int main(int argc, char *argv[])
{
const char *fifoname = "/tmp/fifo-tater"; /* 命名管道文件名,需对应写进程 */
int pipefd, datafd;
int bytes, ret;
char buffer[BUFSIZE];
if (argc != 2) /* 带文件名参数 */
{
fprintf(stderr, "Usage: %s < filename >
", argv[0]);
exit(EXIT_FAILURE);
}
pipefd = open(fifoname, O_RDONLY); /* 打开管道文件 */
datafd = open(argv[1], O_WRONLY | O_CREAT, 0644); /* 打开目标文件 */
if ((pipefd > 0) && (datafd > 0)) /* 将管道文件的数据读出并写入目标文件 */
{
bytes = read(pipefd, buffer, BUFSIZE);
while (bytes > 0)
{
ret = write(datafd, buffer, bytes);
if (ret < 0)
{
perror("write error");
exit(EXIT_FAILURE);
}
bytes = read(pipefd, buffer, BUFSIZE);
}
close(pipefd);
close(datafd);
}
else
{
exit(EXIT_FAILURE);
}
return 0;
}
涉及两程序我暂时没用VS测试,把它编译出来是这样的.(这还有一个我拷贝的文件)

然后执行send程序,记得put到后台,也可以让他daemon啦.send后会阻塞,因为write不了那么多,还有东西没取走,所以就阻塞了,直到recv来取,取完了,他也就完成发了.(因为有他取,他才能继续发.),最后校验两个文件,没有差异.

实际上通信还可以用共享内存和信号量方法,共享内存图示:

而信号量,操作单片机的人都明白.就不用解释了.或者日后再说吧.