有人问起,我就写一篇吧,不然最近也不知道有什么东西好写的.要说进程间通信,首先要明白进程是什么,进程都有什么状态,进程... (说不完了.)
我就先盗图一下.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来取,取完了,他也就完成发了.(因为有他取,他才能继续发.),最后校验两个文件,没有差异.
实际上通信还可以用共享内存和信号量方法,共享内存图示:
而信号量,操作单片机的人都明白.就不用解释了.或者日后再说吧.