基于VS2019 C++的跨平台(Linux)开发(1.3.2)——进程管理

基于VS2019 C++的跨平台(Linux)开发(1.3)——进程管理

接上一篇文章,继续学习进程管理第二部分wait和waitpid,首先来回顾作业

一、回顾作业

第一题

1)实现步骤及注意事项

1、先把路径传进来要先判断是文件还是文件夹,使用保存路径信息的结构体stat,( S_ISDIR(tpstru.st_mode) == 1)表示文件夹;

2、如果是文件夹则打开(opendir),返回目录指针,如果打开失败返回NULL;

3、如果打开成功就读取文件夹(readdir),返回目录结构体指针(注意打开的文件夹里面可能还有子文件夹,如果是文件夹就不需要开子进程去拷贝。 每打开一次都要进行判断,重复的逻辑不知道次数,所以用while循环判断打开的文件夹是否为NULL);

4、如果是文件则拷贝文件,注意通过目录结构体指针获取的名称只是单纯的文件名(di_rent->d_name),如下图,但是我们要根据路径拼接上文件名进行文件拷贝(sprintf、strcat)

5、拼接后的路径要进行清空(bzero函数),不然会拼上上一次的路径(如下图),且提示异常

 6、接下来就要判断这个路径下是否是文件还是文件夹,是文件才开子进程进行拷贝,用stat判断,(注意子进程在做完一系列逻辑后要exit,否则出现子进程循环开子进程),父进程没事情做,只要让他继续找文件开子进程即可

问题:如果找文件的循环走完以后,会出现两种情况:

  • ①父亲刚好走完while循环,孩子也刚好完成拷贝动作,父子一起结束进程
  • ②父进程先走完return,而孩子还在做拷贝动作(文件可能很大很大),父亲先于孩子消亡,孩子变成了孤儿进程,程序永远结束不了(只能kill,不合适)。
  • 要避免第二种情况发生,所以目前只能在找文件的循环外再造一个死循环,让父亲不能先消亡(但如果我们在VS中运行,因为return 那里打了个断点,所以父进程也不会先结束,如果在linux中运行就会造成父进程先结束)。这样虽然避免了进程托孤,但是让父进程永远处在死循环中,不合适,所以要使用wait或者waitpid

拷贝文件时因为不知道文件是二进制还是纯文本(字符)的,所以read的时候首选sizeof(因为所有能在计算机里面存下来的一定的二进制,字符只是二进制之上的东西)。如果想提高效率可以先判断是不是纯文本(思路:判断文件后缀,文件名的拆分切割,比较繁琐)

2)代码

void cpFile(string srcPath, string dstPath)
{
	int pid = 0;
	struct dirent* di_rent;
	DIR* dir;
	//用来保存路径信息
	struct stat tpstru;
	stat(srcPath.c_str(),&tpstru);
	//S_ISDIR 是文件夹返回1
	//S_ISREG 是文件返回1
	cout << "S_ISDIR  " << S_ISDIR(tpstru.st_mode) <<endl;
	if (S_ISDIR(tpstru.st_mode) == 1)
	{
		//opendir返回目录指针
		if ((dir = opendir(srcPath.c_str())) == NULL)
		{
			perror("open dir error");//perror可以查看具体错误
		}
		else
		{
			char filePath[200] = { 0 };
			char dstPath2[200] = { 0 };
			
			while ((di_rent = readdir(dir)) != NULL)
			{
				//cout << "子文件名 " << di_rent->d_name << endl;
				bzero(filePath,sizeof(filePath));
				bzero(dstPath2, sizeof(dstPath2));
                //拼接
				strcat(filePath, srcPath.c_str());
				strcat(filePath, "/");
				strcat(dstPath2, dstPath.c_str());
				sprintf(dstPath2, "%s%s%s", dstPath2, "/copy_", di_rent->d_name);

				strcat(filePath, di_rent->d_name);

				stat(filePath,&tpstru);
				if (S_ISDIR(tpstru.st_mode) == 1)//文件夹
				{
					continue;
				}
				else
				{
					pid = fork();
					if (pid == 0)//子进程
					{
						cout << "子进程 pid =" << getpid()<< "开始拷贝" << endl;
						//拷贝文件
						copyFile(filePath, dstPath2);
						cout << "子进程 pid ="  << getpid() << "拷贝成功" << endl;
						//退出子进程
						_exit(0);
					}
					//if (pid > 0)//父进程
					//{

					//}
				}
			}
			while (1)//让父进程进入死循环,以防他先结束让子进程称为孤儿
			{

			}
			
		}
	}
	else
	{
		cout << "当前路径不正确,不是一个文件夹 " << endl;
	}

}
void copyFile(const char* srcFile, const char* dstFile)
{
	int readfd = 0, writefd = 0;
	int res1 = 0, res2 = 0;
	char buf[1024] = { 0 };
	umask(0);
	//只读的方式打开
	readfd = open(srcFile, O_RDONLY, 0777);
	//文件存在——只写
	writefd = open(dstFile, O_CREAT | O_WRONLY, 0777);
	if (readfd < 0 || writefd < 0)//打开文件失败,返回-1
	{
		perror("open file error");
	}
	else {
		//读取的内容不为空就继续读取
		while ((res1 = read(readfd, buf, sizeof(buf))) > 0)
		{

			res2 = write(writefd, buf, res1);
			//读取完一次就清空,为下次做准备
			bzero(buf, sizeof(buf));
			
		}
		close(readfd);//先关闭读的
		close(writefd);

	}

3)效果

第二题

1)实现步骤及注意事项

打开文件,循环读,读一次,写多次

注意写一次操作后,要注意清空之前拼接的路径

2)代码

void breakFile(string srcFile, string dstFile)
{
	int readfd = 0, writefd = 0;
	int res1 = 0, res2 = 0;
	//char* buf = new char[50];/	只能读取8字节
	char buf[1024000] = { 0 };//存二进制数据
	string dstName = "";
	int tpNum = 0;
	
	char c[90] = { 0 };
	umask(0);
	//只读的方式打开
	readfd = open(srcFile.c_str(), O_RDONLY, 0777);
	//文件存在
	if (readfd < 0 || writefd < 0)//打开文件失败,返回-1
	{
		perror("open file error");
	}
	else {
		//读取的内容不为空就继续读取
		while ((res1 = read(readfd, buf, sizeof(buf))) > 0)
		{
			snprintf(c, 90, "%01d", tpNum);
			//dstName = string(dstFile + "/demo") + string(c) + ".txt";
			dstName = string(dstFile + "/demo") + string(c) + ".avi";
			cout << "文件名 " << dstName << endl;
			writefd = open(dstName.c_str(), O_CREAT | O_WRONLY, 0777);
			cout << "res1 = " << res1 << endl;
			res2 = write(writefd, buf, res1);
			if (res2 > 0)
			{

				//读取完一次就清空,为下次做准备
				close(writefd);
				dstName = "";
				tpNum++;
				bzero(buf, sizeof(buf));
			}
			
		}
		close(readfd);
	

	}
	cout << "breakFile finished " << endl;


}


//main中拆分视频
	breakFile("/root/projects/Warcraft3_End.avi", "/root/projects/breakFIle");

3)效果

 

 

二、wait和waitpid

什么是阻塞?可以理解为堵在了程序某个地方无法往下走

如read函数,读不到任何字符的时候就会阻塞,直到读到一个字符为止。(有一个状态会让它结束阻塞,即读到一个字符)。 阻塞后,这时这个函数也没有返回值,后面一系列所有的函数都不能运行。

阻塞不是死循环,死循环没法结束,但是阻塞是可以结束的,等待某一个状态就结束卡死的状态

1、回顾几种进程

1)僵尸进程:子进程退出,父进程没有收回子进程资源,如PCB,子进程就是僵尸进程

【或者说,当一个子进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行或者父进程调用了wait才告终止】

2)init进程:一号进程(最开始),负责收留孤儿进程,变成他们的父进程

3)孤儿进程:父进程先于子进程死亡,子进程变成孤儿进程,被托管给系统进行管理(进程托孤),他的父进程就成为了一号进程

回顾作业:

前面的作业第一题我们为了解决父进程、子进程哪个先结束,避免出现孤儿或者僵尸,就采用了while死循环的方式,但实际上这样并不能真正解决问题,因为这样会让整个进程卡死,程序无法结束。那么要怎么更好的解决这个问题呢?所以就涉及到了wait和waitpid函数

2、wait和waitpid函数——阻塞式函数

1)wait函数用于使父进程阻塞(一般在父进程调用),直到一个子进程结束或者该进程接收到一个指定的信号(后面会提到)。如果这个父进程没有子进程或者他的子进程已经结束,就会马上返回。

2)wait函数返回说明
调用wait或waitpid的进程可能会:

  • 阻塞(如果其所有子进程都还在运行)。
  • 带子进程的终止状态(exit)立即返回(如果一个子进程已终止,正等待父进程保存或取得其终止状态)。
  • 出错立即返回(没有任何子进程或者子进程出现重大逻辑bug退出)。

如果出现多个子进程,wait会看谁先结束,一结束父进程就会接收到状态码(即父进程不认识所有的子进程,只管谁先结束)。 但如果我想等最后的那个子进程结束,那就要使用waitpid(指定等谁) 

3)wait和waitpid的区别

在一个子进程结束前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
waitpid并不等待第一个终止的子进程,因为它有若干个选择项,可以控制等待的特定进程。
实际上wait函数是waitpid函数的一个特例

共同点:都可以解决子进程僵死问题,让进程处于睡眠可唤醒状态

3)wait和waitpid函数说明:

头文件:

#include <sys/types.h>
#include <sys/wait.h>

函数原型:

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

返回值:若成功则为子进程ID号,若出错则为-1

参数:

1、status:为空时,代表任意状态结束的子进程;不为空,则代表指定状态结束的子进程。

补充:

  • exit函数参数也是int*status,如exit(0)、exit(2);其状态码(可自定义)返回给wait函数或waitpid函数
  • 这个状态码是为了调用wait的时候给到父进程,父进程虽然控制不了子进程,但利用这个状态码来判断当前的子进程是什么状况(执行成功还是失败),比如状态为0表示子进程运行正常,状态为1有问题
  • 这样子就变成父进程可以等待子进程去完成一些重要的逻辑之后,然后父进程根据他的结果来做一些逻辑的控制,比如子进程在做文件拷贝,但是拷贝是否成功父进程不知道,所以使用wait,等待子进程操作完成后,判断反馈回来的结果,再反馈给用户

2、pid:
    <-1 回收指定进程组内的任意子进程
    -1 回收任意子进程
    0 回收和当前waitpid调用一个组的所有子进程
    >0 回收指定ID的子进程
3、options:
    WNOHANG:若由pid指定的子进程不立即可用,则waitpid不阻塞,此时返回值为0
    WUNTRANCED:若实现某支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态
    0:同wait,阻塞父进程,等待子进程退出

示例代码 

int res = 0;
	int pid = 0;
	int status = 0;
	pid = fork();

	if (pid == 0)
	{
		cout << "子进程1 pid ="  << getpid() << endl;
		for (int i = 0; i < 10; i++)
		{
			cout << "子进程1 for i =" << i<< endl;
			sleep(1);
		}
		 
		exit(2);
	}
	else if (pid > 0)
	{
		int pid2 = 0;
		pid2 = fork();
		if (pid2 == 0)
		{
			cout << "子进程2 pid =" << getpid() << endl;
			for (int i = 0; i < 5; i++)
			{
				cout << "子进程2 for i =" << i << endl;
				sleep(1);
			}
			exit(1);
		}
		else if (pid2 > 0)
		{
			cout << "父进程 pid =" << getpid() << endl;
			res = wait(&status);
			if (WIFEXITED(status))
			{
				cout << "父进程 status =" << WEXITSTATUS(status) << endl;
			}
			//如果子进程是死循环,则打印不了以下内容;如果就循环有限次,则可以打印
			cout << "父进程运行wait后返回值 " << res << endl;
			sleep(10);
		}
		
	}
	return 0;

效果

因为第二个子进程先执行完,所以wait的返回值是他的pid

 使用waipid指定等待第一个进程结束,返回他的pid

回顾作业:

所以前面的作业第一题可以使用waitpid代替死循环,但因为while循环产生很多个pid,不确定有多少个,大家可能想到只要等待最后一个子进程(文件一样大的情况),但是不能保证最后一个子进程拷贝的文件最大,在他前面如果有比最后一个子进程拷贝的文件还大,那么这个进程才是最后一个。所以有如下两个方法:

  • 定义一个数组来存pid,遍历这个数组,每一个都waitpid(所有子进程都等待)
  • 判断哪一个文件是最大的,那么它所对应的这个子进程就是最慢结束的,保存它的pid,等待这个pid即可

三、小试牛刀

原创不易,转载请注明出处:
基于VS2019 C++的跨平台(Linux)开发(1.3.2)——进程管理

下面进入第三部分的学习:

基于VS2019 C++的跨平台(Linux)开发(1.3.3)——进程管理

猜你喜欢

转载自blog.csdn.net/hml111666/article/details/123461599
今日推荐