Linux 的进程和进程间通信

/ 0评 / 0

有人问起,我就写一篇吧,不然最近也不知道有什么东西好写的.要说进程间通信,首先要明白进程是什么,进程都有什么状态,进程... (说不完了.)
我就先盗图一下.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[]);

大概总结下:

  1. 后缀 p表示使用 filename 做参数,如果 filename 中包含/则视其为路径名,否则将会在 PATH 环境变量所指定的各个目录中搜索该文件,如 execlp()函数.无后缀 p 则必须使用路径名来指定可执行文件的位置,比如execl()函数.
  2. 后缀 e表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以 NULL 结束,如 execvpe()函数.而无此后缀的函数则将当前进程的 environ 变量复制给新的程序,如
    execv()函数.
  3. 后缀 l表示使用 list 形式来传递新程序的参数,所有这些参数均以可变参数的形式在exec 中给出,最后一个参数需要是 NULL 用以表示没有更多的参数了,如 execl()函数.
  4. 后缀 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来取,取完了,他也就完成发了.(因为有他取,他才能继续发.),最后校验两个文件,没有差异.

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

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

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注