Linux——进程间通信之管道

进程间通信之管道

在这里插入图片描述

文章目录

  • 进程间通信之管道
  • 1. 进程间通信
    • 1.1 为什么要进行进程间的通信
    • 1.2 如何进行进程间的通信
    • 1.3 进程间通信的方式
  • 2. 管道
    • 2.1 匿名管道
      • 2.1.1 系统调用pipe()
      • 2.1.2 使用匿名管道进行通信
      • 2.1.1 匿名管道四种情况
      • 2.1.2 匿名管道的五大特性
      • 2.1.3 进程池
    • 2.2 命名管道
      • 2.2.1 创建命名管道
      • 2.2.2 利用命名管道进行进程间通信
      • 2.2.3 命名管道的一个特性

1. 进程间通信

1.1 为什么要进行进程间的通信

对于这个问题,答案显而易见:一个进程必然不能解决所有的问题,系统中往往需要多个进程的协作来进行工作,而进程间的协作就需要进程之间进行信息的交互,这个过程也叫做进程间的通信

简单来说,进程间的通信可以实现以下功能:

  • 数据传输
  • 进程控制
  • 资源共享
  • 事件通知
  • ………………

1.2 如何进行进程间的通信

由于进程具有独立性,因此两个进程之间不能直接进行数据之间的传输(否则就会破坏进程的独立性)

因此我们需要一片公共的区域来供进程之间进行信息交流,而这片公共区域就是由OS操作系统提供

根据上面的分析,我们也可以得出关于进程间通信的本质:

进程间通信的实质上就是:让不同的进程看到同一份资源

1.3 进程间通信的方式

根据操作系统OS提供公共区域方式的不同,进程间的通信也会有不同的形式,例如:

  • 管道
  • 共享内存
  • 信号量
  • 消息队列

2. 管道

我们来假设这样一个场景:

一个进程同时以读写的方式打开一个文件,那么就会产生两个文件描述符fd:

在这里插入图片描述

此时,创建子进程,子进程会继承父进程属性:

在这里插入图片描述

我们可以发现,这时子进程和父进程就同时看到同一份文件file了。这时,如果我们关闭父进程的写端,再关闭子进程的读端:

在这里插入图片描述

这样,就可以通过子进程向公共文件写数据,父进程向公共文件读数据的方式,进行父子进程之间的通信了。


向上面这样的,基于文件的形式进行进程间通信的方式,叫做管道

通过上面的例子,我们也知道:管道只允许一端读,一端写,因此基于管道的通信都是单向的

管道可以分为匿名管道和命名管道

2.1 匿名管道

2.1.1 系统调用pipe()

系统不允许用磁盘文件当作进程间通信的公共资源,为此系统提供了系统调用pipe()来为我们创建进程通信需要的文件:

#include <unistd.h>
int pipe(int pipefd[2]);
  • 该系统调用会创建并以读写方式打开一个不存在于磁盘且不需要向磁盘刷新的,只存在于内存中的匿名文件(因为这个文件只需用来给两个进程进行通信,不需要持久的保存),这个文件就叫做匿名管道
  • 数组pipefd是一个输出型参数,用于存放匿名管道的fd,其中pipefd[0]存放的是读端;pipefd[1]存放的是写端

2.1.2 使用匿名管道进行通信

使用pipe()创建了系统调用后,我们再创建一个子进程,让子进程继承父进程的数据,此时父子进程就会同时以读写的方式指向同一个匿名管道了。最后只要确定数据传输方向,关闭父进程的读(写)端,关闭子进程的写(读)端,就可以正常进程进程间的通信

同时,我们应该清楚:

由于是通过创建子进程的方式,来让子进程继承父进程的数据,使其指向相同的匿名管道来进行通信,因此匿名管道只能用来进行血缘进程之间的通信(通常用于父子进程)

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd)	//写端操作
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 20){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)	//读端操作
{char buf[1024] = {0};int cnt = 20;while(cnt--){read(rfd, buf, sizeof(buf) - 1); //-1:预留'\0',防止出现错误printf("%s\n", buf);}
}int main()
{int pipefd[2] = {0};	int ret = pipe(pipefd);	//创建匿名管道if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id)	//子进程{//chile(w)close(pipefd[0]);	//关闭读端my_write(pipefd[1]);	//进行写操作exit(-1);}//father(r)close(pipefd[1]);	//关闭写端my_read(pipefd[0]);	//进行读操作wait(NULL);	//等待子进程退出,防止出现僵尸进程return 0;
}

效果如图所示:

在这里插入图片描述

2.1.1 匿名管道四种情况

情况一:写端停止写入并且管道没有数据,那么读端就会阻塞,直到读到数据

例如,将上述代码的my_write()函数修改:

void my_write(int wfd)
{//写端不写
}

重新编译后,运行效果如图:

在这里插入图片描述

可以看到,如果写端不向读端写入数据,并且管道没有数据,读端就会陷入阻塞等待状态

情况二:写端一直在写直到管道写满,读端不读,那么写端就会阻塞等待,直到管道的数据被读取

例如,将上述代码的my_write()、my_read()函数修改:

void my_write(int wfd)
{int cnt = 0;char a = 'A';//每次只写入一个字符,并输出输入的字符个数while(1){write(wfd, &a, 1);printf("cnt: %d\n", cnt++);}
}void my_read(int rfd)
{//读端不读
}

重新编译后,运行效果如图:

在这里插入图片描述

可以看到:当读端不读时,写端向管道写入了65535 + 1个字符后,就进入了阻塞等待状态,这说明管道已经被填满了。同时也可以推出,在博主所用的系统中,默认的管道大小为65536Byte也就是64kB

如果我们再次修改代码,让读端每2秒读取一次管道:

void my_read(int rfd)
{char buf[1024] = {0};while(1){sleep(2);int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)break;printf("%s\n", buf);}
}

重新编译后,运行效果如图:

在这里插入图片描述

可以总结:当管道的部分数据被读取后,写端有重新写入数据了

情况三:写端关闭,当读端读完管道的数据后,读端就会读到管道的末尾(相当于读到文件尾),自动关闭读端

例如,将上述代码的my_write()、my_read()函数修改:

void my_write(int wfd)
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 5){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] = {0};while(1){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)	//如果read的返回值为0,说明写端关闭了fd,读端读到了管道末尾{printf("no data be read, read exit\n");break;}printf("%s\n", buf);}
}

重新编译后,运行效果如图:

在这里插入图片描述

情况四:读端关闭,写端还在写,系统就会通过发送信号的方式强制终止写端(kill -13 child_pid)

将整体代码修改如图:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd)
{const char* str = "i am child process: ";char buf[128] = {0};pid_t id = getpid();while (1){snprintf(buf, sizeof(buf), "message: %s, id: %d\n", str, id);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] = {0};int cnt = 5;while(cnt--){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n){printf("no data be read, read exit\n");break;}printf("%s\n", buf);}printf("read will close\n");sleep(1);close(rfd);
}int main()
{int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id){//chile(w)close(pipefd[0]);my_write(pipefd[1]);printf("child close wfd\n\n");close(pipefd[1]);exit(-1);}//father(r)close(pipefd[1]);my_read(pipefd[0]);int status = 0;int rid = waitpid(id, &status, 0);if (rid == id){printf("child process singal code: %d\n", status & 0x7f);}return 0;
}

重新编译后,运行效果如图:

在这里插入图片描述

2.1.2 匿名管道的五大特性

  • 同步机制:管道在处理数据读写时,确保数据的有序性和正确性的一种控制方式
  • 血缘进程通信:由于匿名管道的构建建立在进程继承的基础上,因此匿名管道只允许血缘进程的通信
  • 单向通信(半双工):同一时间,只允许一端读,一端写
  • 文件的生命周期是随进程的:父子进程退出,管道(文件)自动释放
  • pipe是面向字节流的

2.1.3 进程池

进程池的基本概念

进程池是一组预先创建好的进程集合,这些进程处于空闲等待状态,随时准备接收任务并进行处理。父进程可以在任意时候控制子进程的休眠、工作与退出。

进程池的优点

  • 可以复用进程,从而避免了频繁的调用系统函数,节省了资源开销与时间
  • 使用进程池有利于管理,并充分利用系统资源

实现进程池:

进程池程序myprocesspool的程序流程大致如下:

在这里插入图片描述

实现代码:

task.hpp:

#pragma once#include<iostream>
#include<unistd.h>typedef void(*work_t)(pid_t);
typedef void(*task_t)(pid_t);void task_1(pid_t id)
{std::cout << "task_1" << std::endl;
}void task_2(pid_t id)
{std::cout << "task_2" << std::endl;
}void task_3(pid_t id)
{std::cout << "task_3" << std::endl;
}task_t tasks[3] = {task_1, task_2, task_3};void worker(int channel_index)
{while(1){int task_index = 0;int n = read(0, &task_index, sizeof(int));if (0 == n){std::cout << "write close, channel: " << channel_index << " closing…………" << std::endl; break;}std::cout << "channel: " << channel_index << "  pid: " << getpid() << ": i am working: ";tasks[task_index](getpid());}
}

processpool.cc:

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctime>
#include "task.hpp"#define TIME 5//return value
enum
{USAGE_ERROR = 1,PROCESS_NUM_ERROR, PIPE_ERROR,SELECT_CHANNEL_ERROR,SELEDT_TASK_ERROR
};void usage()
{std::cout << "Usage: ./processpool [process_num]" << std::endl;
}class channel
{
public:channel(std::string name, int rfd, int wfd, pid_t child_id): _name(name), _rfd(rfd), _wfd(wfd), _child_id(child_id){}const std::string& get_name(){return _name;}int get_rfd(){return _rfd;}int get_wfd(){return _wfd;}pid_t get_child_id(){return _child_id;}void close_fd(){close(_wfd);}
private:const std::string _name;const int _rfd;const int _wfd;const pid_t _child_id;
};class processpool
{
public:processpool(int process_num): _process_num(process_num){}//创建管道int create_channel(){std::vector<int> fd;for (int i = 0; i < _process_num; i++){int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return PIPE_ERROR;}pid_t id = fork();if (id == 0){//child: readclose(pipefd[1]);dup2(pipefd[0], 0);//关闭除第一个子进程外的多个写端if (!fd.empty()){for (auto k : fd)close(k);}//workworker(i);std::cout << "channel " << i << " pid: " << getpid() <<  " exit" << std::endl;exit(-1);}//father: writeclose(pipefd[0]);std::string name = std::string("channel: ") + std::to_string(i);channel c = channel(name, pipefd[0], pipefd[1], id);_channels.push_back(c);fd.push_back(pipefd[1]);}return 0;}    int select_channel(){static int c = 0;int ret = c;c = (++c) % _process_num;return ret;}int select_task(){return rand() % 3;}int control_child(){int cnt = TIME;while(cnt--){//选择一个进程(管道),一个任务int channel_index = select_channel();int task_index = select_task();if (channel_index >= _process_num){std::cout << "select channel error" << std::endl;return SELECT_CHANNEL_ERROR;}if (task_index >= 3){std::cout << "select task error" << std::endl;return SELEDT_TASK_ERROR;}//发送任务int wfd = _channels[channel_index].get_wfd();write(wfd, &task_index, sizeof(int));sleep(2);}return 0;}void PrintDebug(){for (auto channel: _channels){std::cout << channel.get_name() << ": rfd: " << channel.get_rfd() << ": child_id: " << channel.get_child_id() << std::endl;}}void clean_wait(){//关闭所有的写端for (auto channel : _channels){channel.close_fd();pid_t id = waitpid(channel.get_child_id(), nullptr, 0);if (-1 == id){perror("waitpid:");}if(id == channel.get_child_id()){std::cout << "wait child: " << channel.get_child_id() << " success\n" << std::endl;}}}
private:const int _process_num;std::vector<channel> _channels;
};//  ./process num
int main(int argc, char* argv[])
{if (1 == argc){usage();return USAGE_ERROR;}int process_num = std::stoi(argv[1]);if (process_num <= 0){std::cout << "process_num should be grearter than 0" << std::endl;return PROCESS_NUM_ERROR;}srand((unsigned int)time(nullptr));std::cout << "process nums: " << process_num << std::endl;//创建进程池processpool* processpool_1 = new processpool(process_num);//创建管道int ret = processpool_1->create_channel();   if (0 != ret){return ret;}std::cout << "create channels complete" << std::endl; //工作ret = processpool_1->control_child();if (0 != ret){return ret;}//回收 processpool_1->clean_wait();return 0;
}

2.2 命名管道

上文提到,匿名管道只适用于血缘进程之间的通信,那么为了解决没有关系进程间通信的问题,操作系统提供了命名管道

2.2.1 创建命名管道

使用命令创建命名管道:

mkfifo [filename]

例如:

在这里插入图片描述

可以看到,通过命令mkfifo fifo在当前目录创建了一个名为fifo的管道文件,同时可以看到,这个管道文件的文件类型为p(pipe)

使用系统调用创建命名管道:

int mkfifo(const char *pathname, mode_t mode);
  • pathname是管道文件的文件名
  • mode是管道文件的权限
  • 返回值:成功返回0;失败返回-1

2.2.2 利用命名管道进行进程间通信

知道了系统调用后,我们就可以利用管道来进行进程间的通信了:

我们用下面的代码进行演示:

头文件:

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define PATH "FIFO"	//默认文件名
#define MODE 0666	//默认权限//命名管道类
class fifo
{
public:fifo(const std::string& name)	//构造函数创建管道文件: _name(name){int ret = mkfifo(_name.c_str(), MODE);if (-1 == ret){std::cerr << "mkfifo error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}std::cout << "fifo made success" << std::endl;}~fifo(){unlink(_name.c_str());	//进程结束时,利用系统调用unlink()删除管道文件}
private:const std::string _name;
};

服务端server:读

#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;	fifo named_pipe(PATH);	//服务端创建管道文件,读端int rfd = open(PATH, O_RDONLY);	char buffer[1024] = {0};while (1){int n = read(rfd, buffer, sizeof(buffer) - 1);if (0 == n)	//返回值为0,说明读到文件尾,即写端关闭{std::cout << "client exit, server also exit" << std::endl;break;}else if (-1 == n){std::cerr << "read error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}else{buffer[n] = 0;std::cout << buffer << std::endl;}}close(rfd);return 0;
}

客户端client:写

#include "common.hpp"int main()
{std::cout << "I am client" << std::endl;int wfd = open(PATH, O_WRONLY);	//客户端,写std::string buffer;while(1){printf("message # ");std::getline(std::cin, buffer);if ("quit" == buffer){printf("client exit\n");break;}int n = write(wfd, buffer.c_str(), buffer.size());if (n != buffer.size()){std::cerr << "write error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}}close(wfd);return 0;
}

效果如图:

在这里插入图片描述

2.2.3 命名管道的一个特性

如果我们在服务端server.cc代码中加一行代码:

#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;fifo named_pipe(PATH);int rfd = open(PATH, O_RDONLY);std::cout << "open success" << std::endl;	//查看客户端是否成功打开管道文件//……………………}

并运行服务端(读端)一段时间后再打开客户端(写端):

在这里插入图片描述

可以看到:在服务端(读端)打开到客户端(写端)未打开的这段时间中,服务端(读端)并没有打开管道文件,而是等客户端(写端)启动后,再打开的管道。

通过这个现象,我们可以得出命名管道的一个特性:

当写端未打开而读端打开时,读端会阻塞,直至写端也打开


本篇完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/887201.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sigrity SPEED2000 DDR simulation模式如何生成和解读DDR仿真报告-SODIMM-Write模式

Sigrity SPEED2000 DDR simulation模式如何生成和解读DDR仿真报告-SODIMM-Write模式 Sigrity SPEED2000 DDR simulation模式如何进行DDR仿真分析操作指导-SODIMM-Write模式详细介绍了如何进行DDR Write模式的仿真分析,下面基于此仿真结果进行DDR报告的输出和解读分析 在workfl…

【机器学习chp7】SVM

参考1&#xff0c;笔记 SVM笔记.pdf 参考2&#xff1a;王木头视频 什么是SVM&#xff0c;如何理解软间隔&#xff1f;什么是合叶损失函数、铰链损失函数&#xff1f;SVM与感知机横向对比&#xff0c;挖掘机器学习本质_哔哩哔哩_bilibili 目录 一、SVM模型 二、构建决策函…

使用Electron将vue2项目打包为桌面exe安装包

目录 一、下载electron模板项目 【electron-quick-start】​ 二、打开项目&#xff0c;安装所有依赖 三、在打exe包的时候报错是因为没有&#xff0c;需要检查并安装之后重新打包&#xff1b; 四、经过这么疯狂的一波操作之后&#xff0c;就可以打包出你想要的exe安装包&am…

摄像机常见的问题及解决方法

文章目录 1)红外网络枪形摄像机白天出现模糊&#xff0c;晚上出现星芒灯2、摄像机夜晚效果调整3、网络摄像机帧率和码流调整4、码流对图像质量的影响 如果你在安装的过程中,出现了以下的问题,请对照下列描述解决你的问题&#xff1a; 1)红外网络枪形摄像机白天出现模糊&#xf…

决策树分类算法【sklearn/决策树分裂指标/鸢尾花分类实战】

决策树分类算法 1. 什么是决策树&#xff1f;2. DecisionTreeClassifier的使用&#xff08;sklearn&#xff09;2.1 算例介绍2.2 构建决策树并实现可视化 3. 决策树分裂指标3.1 信息熵&#xff08;ID3&#xff09;3.2 信息增益3.3 基尼指数&#xff08;CART&#xff09; 4. 代码…

001 数字逻辑概论

1.1 数字信号与数字电路 目标1&#xff1a;what is 数字信号与数字电路 1.1.1.数字技术的发展及其应用 &#xff08;1&#xff09;发展&#xff1a; 发展过程特点: 以电子器件的发展为基础&#xff0c;如下图 电子管时代&#xff1a; 电子管&#xff1b;电子管体积大、重量…

Rust中Tracing 应用指南

欢迎来到这篇全面的Rust跟踪入门指南。Rust 的tracing是一个用于应用程序级别的诊断和调试的库。它提供了一种结构化的、异步感知的方式来记录日志和跟踪事件。与传统的日志记录相比&#xff0c;tracing能够更好地处理复杂的异步系统和分布式系统中的事件跟踪&#xff0c;帮助开…

C语言——break、continue、goto

目录 一、break 二、continue 1、在while循环中 2、在for循环中 三、go to 一、break 作用是终止循环&#xff0c;在循环内遇到break直接就跳出循环。 注&#xff1a; 一个break语句只能跳出一层循环。 代码演示&#xff1a; #include<stdio.h>void test01() {for (…

SSM全家桶 1.Maven

或许总要彻彻底底地绝望一次 才能重新再活一次 —— 24.11.20 maven在如今的idea中已经实现自动配置&#xff0c;不需要我们手动下载 一、Maven的简介和快速入门 Maven 是一款为 Java 项目构建管理、依赖管理的工具(软件)&#xff0c;使用 Maven 可以自动化构建测试、打包和发…

Oracle SQL*Plus中的SET VERIFY

在 Oracle SQL*Plus 中&#xff0c;SET VERIFY ON 和 SET VERIFY OFF 是两个用于控制命令执行前后显示变量值的命令。这些命令主要用于调试和验证 SQL 脚本中的变量替换情况。 一、参数说明 1.1 SET VERIFY ON 作用&#xff1a;启用变量替换的验证功能。当启用时&#xff0c;S…

双因子认证:统一运维平台安全管理策略

01双因子认证概述 双因子认证&#xff08;Two-Factor Authentication&#xff0c;简称2FA&#xff09;是一种身份验证机制&#xff0c;它要求用户提供两种不同类型的证据来证明自己的身份。这通常包括用户所知道的&#xff08;如密码&#xff09;、用户所拥有的&#xff08;如…

【Unity ShaderGraph实现流体效果之Function入门】

Unity ShaderGraph实现流体效果之Node入门&#xff08;一&#xff09; 前言Shader Graph NodePosition NodeSplit NodeSubtract NodeBranch Node 总结 前言 Unity 提供的Shader Graph在很大程度上简化了开发者对于编写Shader的工作&#xff0c;只需要拖拽即可完成一个视觉效果…

力扣—15.三数之和

15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元…

Java项目实战II基于SpringBoot前后端分离的网吧管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着互联网技术的不断发展…

【设计模式系列】责任链模式(十六)

一、什么是责任链模式 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式。其核心思想是将请求的发送者和接收者解耦&#xff0c;通过一个中介链来传递请求&#xff0c;使得多个对象都有可能接收请求&#xff0c;从而避免请求发送者和接…

算法学习笔记(十):位运算、数论等

一.位运算基础 集合与集合之间的位运算 集合和元素 常用函数 1.使两个整数相等的位更改次数 给你两个正帧数 n 和 k&#xff0c;你可以选择 n 的二进制表示 中任意一个值为 1 的位&#xff0c; 并将其改为0&#xff0c;返回使得 n 等于 k 所需要的更改次数&#xff0c;如无法实…

恋爱通信史之完整性

在前面的章节中&#xff0c;介绍了对通信消息的加密&#xff0c;可以保证保密性(机密性)。虽说中间人无法解密通信消息的内容&#xff0c;但是可以篡改通信的消息。在接受者视角来看&#xff0c;是无法识别通信消息是否被篡改。因此&#xff0c;必须引入一种机制&#xff0c;保…

数据结构第一讲

数据结构定义 算法的定义 什么是好算法&#xff1f; 空间复杂度 时间复杂度 例子1 打印1到N之间的正整数 有递归和循环两种方法实现。 但是在数字变大后&#xff0c;递归的方法会导致内存占用过多而崩溃。 而循环则不会 例子2 写程序给定多项式在X处的值 从里往外算的算…

安卓应用安装过程学习

声明&#xff1a;此文章来自http://shuwoom.com/?p60的学习记录 启动式安装 public static final IPackageManager main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {PackageManagerService m new PackageManagerService(context, inst…

输入/输出管理 III(磁盘和固态硬盘)

一、磁盘 【总结】&#xff1a; 磁盘&#xff08;Disk&#xff09;是由表面涂有磁性物质的物理盘片&#xff0c;通过一个称为磁头的导体线圈从磁盘存取数据。在读&#xff0f;写操作期间&#xff0c;磁头固定&#xff0c;磁盘在下面高速旋转。如下图所示&#xff1a; 磁盘盘面…