3.进程相关

操作系统OS—大型计算机系统Mainframe system 文章目录一.大型计算机系统(Mainframe system)1.Batch System(批处理任务)2.Multi-programming3.Time-shared二.计算机系统结构 一.大型计算机系统(Mainframe system) 大型计算机,顾名思义就是最早期体积很大的计算机。现在的大型计算机用来指专门做某件事的主机,比如IBM的主机,银行的主机,医院的主机等等。 主机计算机一词是... 阅读详情

1.关于进程和程序的相关定义

1.1 程序的相关定义

image-20211108111353924

程序通俗来讲就是我们的源代码文件,然后里面还包含了其他的文件信息

程序入口地址:也就是 main 函数的位置

1.2 进程的相关定义

image-20211108112101092

进程需要资源:CPU ,内存

进程是一个抽象定义:它是一系列资源的集合,并不像程序是真真正正存在的

1.3 单道多道程序设计

image-20211108112219464

需要注意:

一个 CPU 一次只能执行一个程序,只不过因为CPU 切换进程时的速度非常快所以我们会以为它执行了多个程序

1.4 时间片

image-20211108112555228

进程调度策略及算法

6 种进程调度算法

  • 先来先服务

  • 最短进程优先

  • 高优先权优先调度算法

    ​ 根据进程在运行过程中是否可以被中断又分为

    • 抢占式
    • 非抢占式
  • 高响应比优先调度算法

  • 时间片轮转法

  • 多级反馈队列调度算法

1.5 并行和并发

image-20211108114808268

1.6 PCB 进程控制块

image-20211108114924962 image-20211108115315030

进程控制块又称进程描述符,可以和文件描述符结合起来

进程控制块记录的是进程的信息,他就是一个 struct 

1.6.1 如何查看 Linux 上限资源的信息

1.查看 Linux 上限资源信息

ulimit -a
image-20211108115609031

2.修改 Linux 上限资源

修改可以运行的进程资源数为 :100

ulimit -u 100 // 括号里面的参数+修改的值

1.7.进程的状态

进程分为三种状态和五种状态两个分类

三种状态分为:就绪态,阻塞态,还有运行态。五种状态分为新建态,就绪态,运行态,阻塞态和终止态。

1.三种状态及三种状态的转换过程

image-20211130102246128

就绪态(->运行):进程具备运行条件,等待系统按照一定的分配方式分配到 CPU 后就可以执行,由就绪态变为执行态。处在就绪状态的进程可能有多个,他们会形成一个队列,那个队列叫就绪队列。

阻塞态(->就绪):又称为等待 wait 或者睡眠 sleep 状态,这时进程正等待某件事情的发生,如输入输出的完成。当资源得到满足后或者数据处理完毕就会进入到就绪状态。

执行态:进程占用 CPU 在运行。

执行->阻塞:正在执⾏的进程因发⽣某等待事件⽽⽆法执⾏,则进程由执⾏状态变为阻塞状态。比如说:输入输出请求;申请主存或者外部存储设备资源;出现了一些故障或者读写错误之类的。。。

执行->就绪:正在执行的程序,因为时间片用完而暂停运行,或者在抢先式优先级调度算法系统中优先级更高的进行将其中断了。

2.五种状态

image-20211108120739534

2.进程的相关指令

image-20211108122025589

直接使用 man 可以查看指令的信息

man ps 

2.1 aux 显示进程的信息

1.查看进程信息

ps -aux
tty // 查看当前正在执行的指令

STAT:进程状态

START:进程开始的时间

TIME:进程持续的时间

COMMAND:执行的哪个命令产生的这个进程

image-20211108122520583

image-20211108122656751

2.进程的相关状态

image-20211108122750212

2.2 ajx 列出与作业控制相关的信息

ps ajx

PPID:父进程 ID

PID:进程 ID

PGID:进程可以分组,组 ID

SID:会话 Session ID ,将组再进行整合

可以将组 ID 和会话 ID 想象成学校学院班级之间的关系

image-20211108123246062

2.3 top 实时显示进程动态

top

直接在实时的窗口中输入以下指令,使用 q 退出实时显示

image-20211108123515572 image-20211108123853199

2.4 kill 杀死进程

kill 进程号
image-20211108123958673

2.4.1 kill -l 查看 kill 的信号

像 9 就是 kill 的一个信号,这个信号是一个宏代表一个操作。有时候发现 kill 不掉一些进程就可以使用信号进行相关操作

kill -l
image-20211108124351958

E.g.

bash 指令是当前控制台的控制程序(对于控制台的操作可以看做是一个进程),系统为了安全起见,使用 kill 2930 指令关闭控制台是无效的。所以就要使用 kill -9 2930 将控制台进程暂停掉

kill -9 2930
kill -SIGKILL 2930 // 直接使用宏也是可以的

image-20211108124451307

2.5 pid_t 进程号变量

关于进程号的相关函数

image-20211108125123365

getpid():得到当前进程编号

getppid():得到父亲进程编号

getpgid():得到组进程编号

3.进程创建:fork

3.1 fork 在父进程中创建子进程

在 lesson18 fork.c 中进行记录

3.1.1 API

image-20211109104740303

当父进程访问这个程序时返回的 id 是 >0 的,当子进程调用这个程序时返回的 id 是 0

3.1.2 代码

下面实现了书写一个程序,在程序中打开一个进程去执行程序中的 for 循环代码

#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
using namespace std;
int main(){
  // 首先创建一个程序,在程序中创建进程,创建的进程会执行这个程序
  pid_t pid = fork(); // 创建一个新的进程
  // 判断当前是父进程还是子进程
  if(pid>1){
    printf("pid:%d\n",pid);
    printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());

  }
  if(pid==0){
    printf("i am child process,pid:%d,ppid:%d\n",getpid(),getppid());
  }
  // 创建 for 循环操作
  for(int i =0;i<100;i++){
    printf("i:%d,pid:%d\n",i,getpid());
  }
  return 0;
}

3.1.3 代码演示

从结果中可以看出,执行这个程序时需要一个进程,这个是系统帮我们开出的,然后在此进程

的基础上人为又开了个进程,这两个进程一同执行程序

** fork 调用时会有两次返回值,一次是在父进程,一次在子进程**

image-20211109112410402

image-20211109112819914

其中 3155 进程就是命令窗口进程

子进程和父进程都会执行这个程序:

这是因为父进程和子进程调用的代码是不一样的。因为父进程和子进程 id 是限制住的,导致执行的代码不一样

image-20211109113231539

3.1.4 子进程和父进程的读时共享写时拷贝(copy-on-write)

子进程是对父进程的复制,内存空间也是直接复制

image-20211109113655523

内存空间的使用情况:

因为是直接复制的虚拟空间,所以两个进程的虚拟空间中数据都是一样的,唯一不一一样的就是保存在栈空间的 pid

我们的程序会根据栈空间的值去判断应该执行哪一段程序

image-20211109113913234

写时拷贝:

写时拷贝参考资料

image-20211109115326053image-20211109115340816

内核此时并不复制整个父进程的空间,而是让父子进程共享同一个地址空间,指针指向同一个变量地址
只用在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间
	如果只对某个变量进行读取:直接读
	写:假设更改 num = 10 的数据。本来都是指向物理空间的 num10 ,当写的时候子进程开辟一个新空间向物理空间写数据

4.进程创建:GDB 实现多进程调试

在 lesson18 hello.c 中进行记录

4.1 代码

这里先创建一个进程,不同的进程会执行不同的函数。

其中父进程打印的是 i ,子进程打印的是 j

#include <stdio.h>
#include <unistd.h>
int main() {
    printf("begin\n");
    if(fork() > 0) {
        printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid()); // line 10
        int i;
        for(i = 0; i < 10; i++) {
            printf("i = %d\n", i);
            sleep(1);
        }

    } else {
        printf("我是子进程:pid = %d, ppid = %d\n", getpid(), getppid()); // line
        int j;
        for(j = 0; j < 10; j++) {
            printf("j = %d\n", j);
            sleep(1);
        }
    }
    return 0;
}    

4.2使用 GDB 进行调试

image-20211109130916819

4.1 GDB 调试父进程

1.添加 GDB 调试

gcc hello.c -o hello -g // 生成 GDB 编译
gdb hello // 使用 GDB 的方式打开软件

2.添加断点

在第 10 行和第 20 行添加断点

b 10 // printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid()); 
b 20 // printf("我是子进程:pid = %d, ppid = %d\n", getpid(), getppid()); 
i b // 查看断点情况
r // 执行程序
image-20211109124145827

下面使用 r 执行代码:

可以看到在执行到父进程的指令时程序停止了,接着执行子进程的程序,直到子进程执行完毕,但是发现,这时候 gdb 还能输入东西

image-20211109125808387
c // 继续执行代码

从下图发现父进程则继续执行

这里对子进程设置的断点没有执行,原因是 GDB 默认调试父进程的程序,不调试子进程产生的断点

image-20211109124923046

4.2 GDB 如何调试子进程

4.2.1 show follow-fork-mode 查看当前 GDB 调试的进程

show folllow-fork-mode

image-20211109125239226

4.2.2 show/set follow-fork-mode [parent(默认)|child] 设置 GDB 跟踪父进程还是子进程

在原有进程上调整执行的进程

set follow-fork-mode child
r // 重新运行程序
image-20211109130637857
n // 进行下一行指令操作

从下图可以看到后面再调试的都是子进程

image-20211109130736578

4.3 show/set detach-on-fork[on|off] 设置调试模式

像上面的程序,当父进行执行到断点的时候子进程还是一直执行的。这个函数可以设置另一个进程不执行

on-调试进程执行时其他进程继续执行

off-调试进程执行时其他进程不执行

show detach-on-fork // 查看调试模式
set detach-on-fork [on|off] // 设置调试模式
image-20211109132313526

4.4 查看,切换调试的进程

info inferiors // 先查看当前执行的进程
inferior +number // 然后切换执行的进程
image-20211109132748393

*指向的地方就是当前执行的继承,当时用进程切换时就可以切换到子进程,这个时候调试得就是子进程的代码

当进入到子进程的程序段后别忘了在写一个 c 程序才会继续执行

c
image-20211109132847485

进程执行完毕

5632 进程已经执行完毕,默认切换成子进程,然后继续再调试子进程

image-20211109133532651
detach inferiors +id // 跳出这个进程的 debug

脱离了 debug 之后程序就会继续执行

image-20211109134203571

5.进程创建:exec 函数族

5.1 函数族函数介绍

image-20211110103934871

5.2 虚拟空间变化

image-20211109225546843 image-20211109225833873

正是因为原函数的虚拟内存会被销毁,所以需要创建一个子进程用于执行调用函数

并且最后子进程不会再返回到父进程当中

5.3.相关调用函数

5.3.1 execl 执行可执行程序和Shell指令

1.执行自定义的某个程序

1.API

image-20211109225915052

这里传入的是文件的执行文件,而不是 C 文件

调用失败后:内核还是继续执行调用函数,调用函数最后是有返回值的。调用成功后执行的是 a.out ,这个是没有设置相应的返回值的

2.代码

#include<stdio.h>
#include<unistd.h>
int main(){
    pid_t pid = fork(); // 创建子进程
    if(pid>0){ // 执行父进程
        printf("i am parent:%d",getpid());
        sleep(1);
    }

    else if(pid==0){ // 执行子进程
        execl("hello","hello",NULL); // 第一个参数一般写可执行程序的名称,最后以 NULL 结尾
        printf("i am child im back");// 这句话不会再输出
    }
    for(int i =0;i<3;i++){ //
        printf("i=%d,pid=%d\n",i,getpid());
    }

    return 0;
}

3.代码演示

image-20211110100140290

需要注意1: 当子进程调用执行文件之后不会再返回到原有调用它的进程当中

可以看到 “i am child im back” 这句话并没有输出,说明这个进程就去执行执行程序不再返回去了

后面再执行 for 循环方法的是父进程

2.执行 shell 指令

1.如何查看系统程序保存在哪

刚才的 execl 需要传入可执行文件的路径,使用以下命令行就可以看到某个执行文件的保存地址,以 ps 指令为例

which +指令(执行文件)
E.g. :which ps

2.执行 Shell 指令

execl("/bin/ps","ps","aux",NULL); // aux 是传递的参数

可以看出执行这个文件时就相当于执行了 ps -aux

image-20211110101527844

5.3.2 execlp 执行可执行文件

1.API

image-20211110102646471

5.4函数族的调用函数命名规则

execlp 结尾是 p ;execl 结尾是 l

p(path) :按 PATH 环境变量指定的目录搜索可执行文件

所以定义 execlp 时如果不传入文件的绝对路径依旧可以通过环境变量找到该文件

但是 execl 没有 p ,所以如果调用 execl 方法不传入具体的绝对路径或者可执行文件没有和 .c 文件在一个目录下,则这个可执行文件就找不到,并且不会被执行

l(list):参数地址列表,以空指针结尾

可以看到两个方法都有 l ,并且我们传入的参数是一个字符串(参数地址列表),以NULL 结尾

下图中 execv 使用的是 v 出入的就是参数数组

image-20211110103737103

6.进程控制

6.1 进程退出

6.1.1 exit 退出进程

image-20211110105046610

C 的 exit() 标准库函数会进行函数处理操作和 IO 刷新缓冲区的操作

Linux 则不会

1.API

image-20211110111051469

2.代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
        printf("Hello\n");
        printf("world");
        _exit(0); // Linux
        //exit(0); // C
        return 0;
}

3.代码演示

(1) Linux 中的输出

可以看出没有输出 world ,这是因为 C 会将字符串保存在缓冲区中,而 \n 具有对缓冲区的刷新操作,所以Hello 会打印出来,但是 _exit() 是没有刷新缓冲区的操作的,所以这里的 world 还是保存在缓冲区内

image-20211110110252266

(2)C 标准库的输出

这里输出的 world 是因为 C 的 exit 会对缓冲区内的字符串进行刷新操作

image-20211110110901388

6.2进程相关定义

6.2.1 孤儿进程

1.解释说明

image-20211110111629453

父进程已经运行完,但是子进程还没运行完,父进程没有办法回收子进程资源,init 进程就会回收孤儿进程 ,init 的 ppid 为 1。孤儿进程没有什么危害

2.代码

#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
int main(){
        // 首先创建一个程序,在程序中创建进程,创建的进程会执行这个程序
    pid_t pid = fork(); // 创建一个新的进程
        // 判断当前是父进程还是子进程
    if(pid>1){
        printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());

    }
    if(pid==0){
         sleep(1);
        printf("i am child process,pid:%d,ppid:%d\n",getpid(),getppid());
    }
    // 创建 for 循环操作
    for(int i =0;i<3;i++){
        printf("i:%d,pid:%d\n",i,getpid());
    }
        return 0;
}

3.代码演示

image-20211110112741943

①:父进程先执行,看到其 id 和基于 bash 的 ppid

②父进程已经执行完毕,这时候父进程被其父进程 bash 回收,所以在这里出现了控制台的指令

③子进程开始执行,但是本应该调用他的父进程已经被销毁,所有由 init 去执行和销毁它,ppid 是1

6.2.2 僵尸进程

代码保地址:/home/xu/C++/lesson20 /zombie.c

1.定义

子进程的 PCB 需要由父进程释放,一个进程内核区的 PCB 没有被释放掉,残留的资源一直存放在内存中,并且不能用 kill -9 杀死。但是系统所能使用的进程号有限,导致没有办法创建新的进程,僵尸进程是有害的进程。

2.代码

#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
int main(){
    // 首先创建一个程序,在程序中创建进程,创建的进程会执行这个程序
    pid_t pid = fork(); // 创建一个新的进程
    // 判断当前是父进程还是子进程
    if(pid>1){
        printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
        while(1){
            sleep(1);
            printf("i am zombie\n");
        }

    }
    if(pid==0){
        printf("i am child process,pid:%d,ppid:%d\n",getpid(),getppid());
    }
    return 0;
}

3.代码演示

image-20211110115602351

子进程已经执行完 < defunct >,父进程还在执行,所以父进程没有办法释放子进程的空间,那么子进程就变成了僵尸进程

6.2.3 进程回收-wait 函数使用

image-20211110120907998

进程退出时内核回收资源,主要是 PCB 中的信息

1.API

image-20211110121358927

返回值设置:

子进程状态改变:返回状态改变的子进程 pid

子进程状态没有改变 return 0

失败:返回 -1

此函数的阻塞行为:

调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)

如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.

2.代码

1.不获得子进程被杀死时的状态

首先先创建 5 个子进程

父进程进入无限循环状态:首先打印一个 id ,然后使用 wait 函数将父进程阻塞

子进程也要执行一段无限循环的代码,但是在父进程在阻塞的情况下可以用 kill -9 将子进程杀死

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
    //创建 5 个子进程
    pid_t pid;
    for(int i=0;i<5;i++){
        pid=fork();// 为了避免孙子进程,使用相同的pid 创建子进程
        if(pid==0) break; // 子进程不进行任何操作
    }
    if(pid>0){
        // 父进程一直执行
        while(1){
            printf("parent:%d\n",getpid());
            // 父进程在打印一句话之后进入阻塞状态
            int ret = wait(NULL); // 传入 NULL 则不处理任何状态
            if(ret==-1){ // 父进程阻塞失败或者没有子进程可以杀了,返回 -1
                printf("all child die,pid=%d\n",ret);
                break;
            }
            // 所有的子进程全部处理完毕,打印
        }
    }
    else if(pid==0){
        while(1){
            // 子进程操作
            printf("child:%d\n",getpid());
            sleep(1);
        }
    }
    return 0;
}
image-20211110130444851

2.获得子进程被杀死时的状态

image-20211110132040456

父进程相关判断代码:

int st; // 定义一个赋值参数,函数可以向这个参数赋值
int ret = wait(&st); // 让父进程等进程结束

if(ret == -1) { 
    break;
}
// 判断子进程杀死的状态
if(WIFEXITED(st)) {
    // 是不是正常退出
    printf("退出的状态码:%d\n", WEXITSTATUS(st));
}
if(WIFSIGNALED(st)) {
    // 是不是异常终止
    printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
}

子进程相关退出代码:

exit(0); // 在子进程相应的代码块中调用 exit ,在 exit 中传入相应参数则父进程的 wait 函数就会接收到
kill -9 pid // 下方显示的效果

image-20211110134016646

4.知识扩展–如何创建子进程

pid_t pid;
for(int i=0;i<5;i++){
    pid=fork();// 为了避免孙子进程,使用相同的pid 创建子进程
    if(pid==0) break; // 子进程不进行任何操作
}

如何防止创建孙子进程。因为子进程也是需要执行程序文件的。如果不断的使用 fork()去调用子进程执行到这个方法还会在创建子进程的子进程,所以要想创建多个子进程就要用同一个 fork 方法

虽然使用的都是相同的 pid ,但是子进程的 id 却不同

image-20211110124015900

6.2.4 进程回收-waitpid 方法使用

wait() 方法在进程回收时只能阻塞父进程,waitpid 方法回收进程时有更多的选项

**1.API **

image-20211111103215881

2.代码

int st;
int ret = waitpid(0,&st,0);// 监控进程变化

完整代码

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
    pid_t pid;
    // 创建 5 个子进程
    for(int i=0;i<5;i++){
        pid = fork();
    }
    // 判断当前进程
    if(pid>0){
        while(1){
            printf("i am parent:%d",getpid());
            // 监控进程
            int st;
            int ret = waitpid(0,&st,0);// 监控进程变化
            if(ret==-1){
                break;
            }
            else if(ret==0){ //说明还有子进程没有改变状态
                continue;
            }
            else if(ret>0){ // 如果子进程状态发生了变化
                if(WIFEXITED(st)){
                    // 是不是正常退出
                    printf("退出的状态码:%d\n", WEXITSTATUS(st));
                }
                if(WIFSIGNALED(st)) {
                    // 是不是异常终止
                    printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
                }
                printf("child die, pid = %d\n", ret);
            }
        }
    }
    else{
        while(1){
            printf("child pid%d\n",getpid());
            sleep(1);
        }
        exit(0);
    }
    return 0;
}

3.代码演示

代码演示像上面一样

面试:7.进程通信 IPC

7.1 相关概念

1.什么是 IPC

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC 的⽅式通常有管道

(包括⽆名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams 等。其中 Socket 和 Streams ⽀

持不同主机上的两个进程 IPC。

Linux 中如何进行进程通信

image-20211111114433675

7.2 管道

7.2.1 管道定义

image-20211111115635646

匿名管道就是我们俗称的管道。ls 可以理解为一个文件,这个文件实现的是将当前目录下的所有文件名显示到终端。但是管道有一个重定向作用,它不将文件名显示在终端而是显示在另一个进程文件 wc 中

管道就是在内存中的缓冲器

7.2.2 管道的特点

image-20211111121048870

1.管道是一个保存在内存中的缓冲区,保存在内核空间

2.管道和有名管道的区别

3.管道是一个字节流

4.管道读写的数据是顺序的,数据结构是队列

5.管道是半双工,具有固定的读端和写端

单工:遥控器只能向电视发送信号,但是电视不能向遥控器发送信号

半双工:类似对讲机,虽然两个人可以相互发消息,但是有一方发消息的时候另一方就要等着

双攻:二者同时相互的发送消息

6.管道数据被读走就永远被抛弃,管道的数据结构是循环队列

当在队列中前面的数据产生删除后,队列还是可以被利用

image-20211111122306527

7.匿名管道只能在有关系的进程间通信

比如说父子进程,他们使用的是同一个文件描述符表,所以父进程的文件描述符 fd 对应的管道也和子进程文件描述符对应的管道是一样的

但是如果是无关的进程,虚拟空间不存在同一指向问题,所以相同的文件描述符 fd 对应的可能是不同的管道

7.3管道的相关函数

7.3.1 pipe–创建匿名管道

1.API

image-20211111130834690

一定要在 fork 之前创建管道,否则进程已经创建完了管道不一定对应的是同一个管道。

pipe 返回的是文件描述符,一个对应管道读端,一个对应管道写端

管道默认是阻塞状态,也就是说子进程如果向父进程发送数据,但是管道中没有数据, read 阻塞,如果管道满了 write 阻塞

2.代码

实现子进程向父进程发送数据

int ret = pipe(pipefd); // 创建管道,得到管道输入端和输出端的文件描述符
write(pipefd[1],str,strlen(str)); // 先向管道中写入数据
read(pipefd[0],buf,sizeof(buf)); // 从管道中读取数据
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<string.h>
int main(){
    // 定义传出参数
    int pipefd[2];
    // 在创建子进程之前创建管道
    int ret = pipe(pipefd);
    if(ret==-1) perror("pipe");
    // 创建子进程
    pid_t  pid;
    pid = fork(); 
    if(pid>0){ // 父进程读取数据
        char buf[1024]={0};
        int len = read(pipefd[0],buf,sizeof(buf)); // 传入读取端的描述符
        printf("parent recv:%s,pid:%d\n",buf,getpid());
    }
    if(pid==0){// 子进程写入数据到管道
        char* str = "hello world";
        write(pipefd[1],str,strlen(str)); // 传入写入端的描述符
    }
    return 0;
}

PS: 视频末尾还有一个 while 循环的事实消息发送我没有实现

3.代码演示

从下面就可以看到父进程收到了子进程发来的消息

image-20211111132828255

4.管道之间的相互通信

7.3.2 alimit Linux 指令查看管道大小

ulimit -a
image-20211111135201280

每个管道中有 8 块,每一块是 512 的大小,4K

更改管道大小

ulimit -p

7.3.3 fpathconf–查看管道大小

关键代码

long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
printf("pipe size : %ld\n", size);
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {

    int pipefd[2];

    int ret = pipe(pipefd);

    // 获取管道的大小
    long size = fpathconf(pipefd[0], _PC_PIPE_BUF);

    printf("pipe size : %ld\n", size);

    return 0;
}

执行文件输出管道大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CTYKR2ic-1644492619031)(/Users/xuguagua/Documents/typora_image/image-20211111135454268.png)]

7.3.4 C 实现 Linux 管道通讯指令

代码在 lesson22 parent_child.c

image-20211112105848647

其实在 Linux 中指令 ps aux | grep xxx 操作就是实现 ps 进程和 grep 进程之间的通讯

1.代码

Step1:

创建一个管道:用于两个进程通信

Step2:

创建子进程,对父进程和子进程判断

Step3:

子进程:

​ 先将 ps 原先要输出的地方进行重定向,将控制台的输入输出输入到管道

​ 执行 ps 指令

父进程:

​ 读取管道中的数据

int main(){
    // 0.首先创建管道
    int fd[2];
    pipe(fd);
    // 1.创建子进程
    pid_t pid = fork();
    // 2.判断进程
    if(pid>0){ // 进行读操作
        // 关闭写端
        close(fd[1]);

        // 从管道中循环的取数据
        int len =-1;
        char buf[1024]={0};
        while((len=read(fd[0],buf,sizeof(buf)-1))>0){
            // 数据的处理操作
            printf("%s",buf);
            memset(buf,0,1024); // 清空 buf 中的值
        }
        wait(NULL); // 阻塞自己等待子进程执行完毕
    }
    else if(pid==0){ // 写操作
        // 关闭读端
        close(fd[0]);
        // 文件描述符重定向:将数据在控制台打印
        dup2(fd[1],STDOUT_FILENO);
        // 执行 ps aux
        execlp("ps","ps","aux",NULL);
        exit(0);
    }
    return 0;
}

2.代码演示

可以看到读取到了相关的进程数据

image-20211112114626313

3.需要注意

某一个进程不可以同时进行读操作或者写操作,所以当一个进程进行读操作时就要将写操作进行关闭

close(fd[1]); // 关闭写操作

7.4 管道的读写特点

读管道:

​ 管道中有数据:read 返回实际读到的字节数。==》 这是正常情况

​ 管道中无数据:

​ 写端被完全关闭,read 返回 0 ,相当于读到文件末尾

​ 写端没有完全关闭:read 以为写端还要写入数据,read 阻塞等待

写管道:

​ 读端全部关闭:写管道本想写,但是没有读端,进程异常终止 (进程收到 SIGPIPE信号)

​ 管道读端没有全部关闭:

​ **管道以满:**这时候不能再向管道中写入数据,write 阻塞

​ **管道没有满:**write 将数据写入,并返回实际写入的字节数 ==》 这是正常情况

7.4.1 设置管道非阻塞

管道就是文件描述符,如果想要设置管道非阻塞就要设置文件描述符为 open 状态

更改文件状态 fcntl

1.API

image-20211112121659547

2.读操作阻塞时的代码

在子进程中每发送完一段数据 write 端就进行阻塞,即 sleep 操作,写操作是没有被完全关闭的,这时候读操作阻塞

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
/*
    设置管道非阻塞
    int flags = fcntl(fd[0], F_GETFL);  // 获取原来的flag
    flags |= O_NONBLOCK;            // 修改flag的值
    fcntl(fd[0], F_SETFL, flags);   // 设置新的flag
*/
int main() {

    // 在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }

    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        // 父进程
        printf("i am parent process, pid : %d\n", getpid());

        // 关闭写端
        close(pipefd[1]);

        // 从管道的读取端读取数据
        char buf[1024] = {0};
        while(1) {
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("len : %d\n", len);
            printf("parent recv : %s, pid : %d\n", buf, getpid());
            memset(buf, 0, 1024);
        }

    } else if(pid == 0){
        // 子进程
        printf("i am child process, pid : %d\n", getpid());
        // 关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while(1) {
            // 向管道中写入数据
            char * str = "hello,i am child";
            write(pipefd[1], str, strlen(str));
            sleep(5);
        }

    }
    return 0;
}

3.代码演示

image-20211112122936052

5.更改文件描述符属性,让 read 不阻塞

关键代码:

需要在父进程中添加对读指令的 open 操作,使用 fcntl 指令

int flags = fcntl(pipefd[0], F_GETFL);  // 获取原来的flag
flags |= O_NONBLOCK;            // 修改flag的值
fcntl(pipefd[0], F_SETFL, flags);   // 设置新的flag

父进程的进程操作如下:

int flags = fcntl(pipefd[0], F_GETFL);  // 获取原来的flag
flags |= O_NONBLOCK;            // 修改flag的值
fcntl(pipefd[0], F_SETFL, flags);   // 设置新的flag
while(1) {
    int len = read(pipefd[0], buf, sizeof(buf));
    printf("len : %d\n", len);
    printf("parent recv : %s, pid : %d\n", buf, getpid());
    memset(buf, 0, 1024);
    sleep(1);
}

6.设置不阻塞后代码演示

和上面代码相比这里 read 不阻塞后会一直打印数据,只不过这时候管道中没有数据,所以打印的数据就是 -1

image-20211112124138013

7.3 有名管道

(1)FIFO 可以在⽆关的进程之间交换数据,与⽆名管道不同;

(2)FIFO 有路径名与之相关联,它以⼀种特殊设备⽂件形式存在于⽂件系统中。

有名管道可以进行非“关系” 间的节点通信,就定义了一个文件,两个进程通讯时就是对那个文件进行读写操作

数据结构:关系队列

与普通文件的区别是,有名管道生成的文件在读出之后就不存在了

7.3.1 使用 Linux 命令行创建有名管道 fifo

1.指令

mkfifo fifo // 定义一个名字叫 fifo 的有名管道

2.命令演示

创建完 fifo 之后,在所在目录下生成了一个 fifo 的文件

image-20211112134756724

这里向 fifo 这个文件写入数据 hello world 。写入完数据后进程阻塞,是因为这个 fifo 在等待读取

image-20211112134651875

7.3.2 mkfifo 函数创建有名管道

代码在lesson23

1.API

image-20211112134935892

这里赋予的文件权限类似于文件的文件权限

2.代码:实现两个进程间的通信

①write 文件,创建有名管道,并向管道中写数据

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include<string.h>
int main(){
	// 创建管道之前先判断管道是否存在
	int ret = access("test",F_OK); // 判断一个文件是否存在
	if(ret==-1){ // 管道不存在返回 -1 
		printf("管道不存在正在创建中");
		mkfifo("test",0664);
	}
	// open 管道文件的文件描述符
	int fd = open("test",O_WRONLY);
	for(int i =0 ;i<100;i++){
		char buf[1024];
		sprintf(buf,"hello,%d\n",i);
		printf("write data:%s\n",buf);
		write(fd,buf,strlen(buf));
		sleep(1);
	}
	close(fd);
	return 0;
}

②read,创建读操作,将数据读出

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(){
	// 打开管道
	int fd = open("test",O_RDONLY);
	char buf[1024] = {0};
	while(1){
		int len = read(fd,buf,sizeof(buf));
		if(len==0){
			printf("写端断开连接");
			break;
		}
		printf("recv buf:%s\n",buf);
		memset(buf,0,1024);
	}
	close(fd);
	return 0;
}

3.代码演示

首先先执行 write.c 的文件,但是因为 read 还没有打开,所以这时候的write是阻塞状态

image-20211112152223190

然后执行 read 文件,read 文件就接着可以显示读到的数据

image-20211112152710312

与此同时 write 控制台也开始写文件

image-20211112152836247

当关掉其中一个时另一个也就不读或者不写了,这是因为发生了读、写操作的阻塞

image-20211112152912272

7.4 聊天功能实现–有名管道实战

代码详见 lesson24

7.4.1 整体流程

这里需要创建两个管道,因为一个进程只能控制管道的一端,如果要实现双向通信需要两个管道。

image-20211113095304185

7.4.2 代码

导入相关库文件

#include <sys/types.h>
#include<stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<stdlib.h>
#include<string.h>

1.A 端代码

①创建有名管道

创建有名管道时判断有名管道文件是否存在

// 创建有名管道1
    int ret = access("fifo1",F_OK);
    if(ret==-1){
        printf("管道 1 创建中。。。\n");
        ret = mkfifo("fifo1",0664); // A 对于管道1 使用只写的方式打开
        if(ret==-1){
            perror("mkfifo");
            exit(0);
        }else{
            printf("打开管道1成功。。。\n");
        }
    }
    // 创建有名管道2
    ret = access("fifo2",F_OK);
    if(ret==-1){
        printf("管道 2 创建中。。。\n");
        ret = mkfifo("fifo2",0664); // A 对于管道1 使用只写的方式打开
        if(ret==-1){
            perror("mkfifo");
            exit(0);
        }else{
            printf("打开管道2成功。。。\n");
        }
    }

②分别获得两个管道的写入端和读取端

// 对于两个管道的操作
int fd1 = open("fifo1",O_WRONLY);
int fd2 = open("fifo2",O_RDONLY);

③ while 循环监听两端读取操作

char buf[128];
while(1){ // A 的监听操作
    memset(buf,0,128);
    fgets(buf,sizeof(buf),stdin); // 最后一个参数是从哪里获得这里是从控制台的标准输入输出获得
    write(fd1,buf,sizeof(buf));
    memset(buf,0,128);
    // 定义读操作
    ret = read(fd2,buf,sizeof(buf));
    printf("A recv:%s\n",buf);
}

④关闭两端

// 关闭两个管道
close(fd1);
close(fd2);

2.B 端关键代码

①获得两个管道的通配符

// 对于两个管道的操作
int fd1 = open("fifo1",O_RDONLY);
int fd2 = open("fifo2",O_WRONLY);

② while 循环监听输入输出操作

char buf[128];
while(1){ // B 的监听操作
    memset(buf,0,128);
    // 定义读操作
    ret = read(fd1,buf,sizeof(buf));
    printf("B recv:%s\n",buf);
    memset(buf,0,128);
    fgets(buf,sizeof(buf),stdin); // 最后一个参数是从哪里获得这里是从控制台的标准输入输出获得
    write(fd2,buf,sizeof(buf));
}

7.4.3 代码演示

下图所示,AB 两端的文件都可以收到

现在的 Bug 就是读写都在一个进程中:

有可能发生阻塞的情况。

比如 A 代码中先定义写操作,这个时候 B 向 A 发送消息 A 是读不到的

但是 B 先定义的是读操作,如果 A 先给 B 发送消息 ,B 可以正常读到

image-20211113111215469
【传奇开心果系列】Flet框架实现的窗口屏幕居中示例自定义模板特色和实现原理深度分析 本文介绍了使用Flet框架实现窗口屏幕居中的方法。通过设置page.window.center()方法,程序会自动计算屏幕尺寸和窗口位置实现居中显示。文章建议将任务栏设为自动隐藏以获得更精确的居中效果,并详细分析了Flet窗口管理的实现原理,包括坐标计算、跨平台兼容性等技术细节。该方案提供了简洁的API调用,支持动态调整窗口位置和多显示器环境,源码可通过优快云下载。 阅读详情

相关推荐

量子通信:初识量子城域网

对量子城域网的概念、作用、拓扑、建设内容等进行了较为全面的初步介绍。

量子领域相关话题 3347

分配给进程的资源有哪些

这些资源的分配和管理由操作系统负责,确保进程之间的公平共享和系统的高效运行。进程的资源需求取决于其执行的任务和应用程序的要求。

qq_46130027的博客 2602

狂神说docker(最全笔记)

一.Docker入门 1. Docker 为什么会出现 2. Docker的历史 3.Docker最新超详细版教程通俗易懂 Docker是基于Go语言开发的!开源项目 官网 官方文档Docker文档是超详细的 仓库地址 4. 虚拟化技术和容器化技术对比 4.1. 虚拟化技术的缺点 资源占用十分多 冗余步骤多 启动很慢 2.2. 容器化技术 比较Docker和虚拟化技术的不同 传统虚拟机, 虚拟出一条硬件,运行一个完整的操作系统,然后在这个系统上安装和运行软.

qq_21197507的博客 5万+

基本概念

一、什么是程序?进程?线程?多线程 1.程序(Program):本身是指令的集合,是为让计算机执行某些操作或者解决某个问题而编写的一系列有序指令的集合。通常由一段代码+数据+资源文件组成(涉及语法+算法+数据结构) 2.进程(Process):运行程序的实体,某个程序运行起来就形成了一个进程。 3.线程(Thread):线程是进程中一个独立运行的单位,是进程中一个单一顺序的控制流,也是操作系统能够...

weixin_43732570的博客 6171

五、2K0300 久久派开发板的GPIO控制与Shell脚本自动化实战

本文详细介绍了基于龙芯2K0300处理器的久久派开发板的GPIO控制方法,通过sysfs文件系统实现LED控制和传感器读取。重点讲解了Shell脚本自动化实战,包括LED闪烁、流水灯效果和传感器数据采集,为物联网项目开发提供完整解决方案。

silver的专栏 687

从图像处理到AI:手把手教你用Halcon DL Tool完成第一个缺陷检测项目

本文详细介绍了如何使用Halcon深度学习工具(DL Tool)完成金属表面缺陷检测项目。从环境准备、数据采集标注到模型训练与部署,手把手指导工程师快速构建AI质检系统,显著提升工业缺陷检测效率。特别针对金属反光等难点问题提供实用解决方案。

weixin_42531886的博客 289

程序是什么?

老衲 又有不懂得了,问了这么一段话,很值得思考呀 #include <stdio.h> int main(int argc,const char* argv[]) { printf("hello,world"); return 0; } 老衲说,你看看这个,我上c语言的时候,老师就直接把这个写出来了,也没告诉我这为啥这么写呀! 不着急,来听我,慢慢絮叨! 上面这段是什么?难道是程序吗?其实并不是,上面这段是代码,那么程序又是什么呢? 程序是指示计算机每一步动作的指令,也是一.

Xiao_qsn的博客 6074

【上海大学计算机组成原理实验报告】七、程序转移机制

学习实现程序转移的硬件机制//掌握堆栈寄存器的使用//手动方式实现子程序调用转移过程//手动方式实现子程序返回转移过程//编程实现`OUT`寄存器交替显示`11`和`55`//若要求`11`和`55`各显示`50`次后停机,应该如何修改程序?//根据实验指导书的相关内容,实验箱系统的程序转移硬件机制在于,当`LDPC`有效时,如果此时`DUBS`上的值就是转移的目标地址,则此目标地址被打入`PC`,从而实现程序的转移;若`LDPC`为`0`是附带条件的,就形成“条件转移”。

HackerKevon的博客 2472

计算机的程序、进程与线程

计算机程序(Computer Program),又称计算机软件,是一组计算机能识别和执行的指令,运行于电子计算机上,用于完成特定的任务,满足人们某种需求的信息化工具。程序就如同以某种语言(程序设计语言)写作的文章,要让懂得这种语言的人(编译器)同时也会阅读这篇文章的人(结构体系)来阅读、理解、执行这篇文章。• 定义:线程是计算机科学中的基本概念,指的是在一个进程中执行的独立指令流。• 资源共享:通常,一个进程可以包含多个线程,它们共享进程的资源,如内存空间、文件句柄等,但每个线程有自己的独立执行流。

2401_87710985的博客 2598

Opencascade 保存点云为 .stp 和 .step 格式

总结一下,OpenCascade提供了强大的功能和工具,使我们能够方便地将点云数据保存为 .stp 和 .step 格式。通过使用类似于上述示例代码的方法,你可以使用OpenCascade库将点云数据保存为.STP和.STEP格式。总结一下,使用OpenCascade库,你可以方便地将点云数据保存为.STP和.STEP格式。通过使用类似于上述示例代码的方法,你可以将点云数据转换为OpenCascade的几何模型,并将其保存为所需的文件格式。请注意,上述代码仅涵盖了将点云数据保存为.STP格式的基本步骤。

CwzCode的博客 1157

logisim中,如何设计一个位于十字路口的交通信号灯控制系统?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值。

**My Coding Family** 1342

5.进程相关3

10.共享内存 10.1 基本概念 这种进程之前通信的速度更快,因为是直接操作内存,内核的参与度更少。 ​ 如果是管道:先将要传输的数据从管道中拷贝到内核区,然后内核1将内核中的数据发送给内核2。内核2再将数据拷贝到进程2的用户区中所以处理速度比较慢。 并且可以实现多个进程之间的内存共享 10.2 共享内存调用步骤 1.创建共享内存并获得标识符 2.将这块共享内存添加到调用进程的虚拟内存中 3.得到这个共享内存在虚拟内存中的起始位置 ////// 开始使用共享内存,直到使用完毕 4.将共享内存与进程

许挂挂的博客 402

锂电池SOH预测 | 基于BP神经网络的锂电池SOH预测(附matlab完整源码)

锂电池SOH预测 | 基于BP神经网络的锂电池SOH预测(附matlab完整源码)

天天酷科研的博客 1114

linux进程相关操作,Linux基础(3)—— Linux进程的相关操作

运行进程时,进程会占据前台终端,有时想要将进程放入后端工作,怎么做呢?下面介绍几种方法。1 后台运行程序1.1 &用法:command &用途:使程序在后台运行用之前做过的Minio服务来举例子:./minio server /m1 /m2 /m3 /m4 &1.2 ctrl + z可以将一个正在前台执行的命令放到后台,并且处于暂停状态。1.3 nohup用法:nohup ...

weixin_39757743的博客 167

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

W3AF进程调度相关总结

标签(空格分隔): web扫描器w3af不同插件间调用采用进程池调度,流程如下: 进程池建立 通过plugins.py的plugin_inst.set_worker_pool(self._w3af_core.worker_pool)创建进程池,其中self._w3af_core.worker_pool调用w3afcore.py的worker_pool()方法,set_worker_p

super_xuyao的博客 716

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【Android Framework系列】第3章 Zygote进程相关

Zygote是Android中最重要的一个进程,Zygote进程和Init进程、SystemServer进程是Android最重要的三大进程。Zygote是Android系统创建新进程的核心进程,负责启动Dalvik虚拟机,加载一些必要的系统资源和系统类,启动system_server进程,随后进入等待处理app应用请求。

Yvan 2213

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Linux系统编程3.进程相关

进程: 程序:死的。只占用磁盘空间。–剧本 进程:活的。运行起来的程序,占用内存、cpu等系统资源。–戏 PCB进程控制块:是一个结构体 包含: 进程id 文件描述符表 进程状态:初始态、就绪态、运行态、挂起态、终止态; 进程工作目录位置 *umask掩码 信号相关信息资源 用户id和组id fork函数: pid_t fork(void) 创建子进程。父子进程各自返回进程pid。子进程返回0; ...

qq_42027408的博客 117

进程相关的系统调用3

说明:只供学习交流,装载请注明出处   一,进程等待:   wait函数 头文件 #include #include 函数原型 pid_t wait(int *status); 返回值 成功 失败 是否设置errno 结束的子进程的PID和结束状态 -1

angelo 734

windows c++ (3) windows获取进程PID、进程路径、进程启停及文件相关操作

windows c++ (3) windows获取进程PID、进程路径、进程启停及文件相关操作

勤又氪猿的博客 5123

操作系统-进程(3)Linux下的进程相关命令

操作系统给予这个内存中的单元一个标识符(PID)依据登入者的UID/GID(/etc/passwd) 衍生出的其它程序(子程序),一般情况也,也会沿用这个程序(父程序)的相关权限 ParentID(PPID) [root@localhost ~]# bash <==在当前用户环境下再触发一次bash,原bash会sleep [root@local...

156

Linux的进程管理之相关数据结构—3

先回顾下之前的定义,进程调度是指操作系统按照某种策略或者规则选择进程占用CPU进行运行的过程;即什么时候调度(调度时机)、怎么调度(上下文切换)、按照什么方式调度(调度策略)

weixin_43708235的博客 1294

Linux操作系统概述3——进程相关操作讲解(进程概念、xinetd守护进程、进程管理命令)

相关文件路径/etc/xinetd.conf 该文件为xinetd服务的主配置文件/etc/xinetd.d 该目录下存放着被托管的服务的目录(可以理解为主配置文件的补充)主配置文件字段介绍主配置文件由多个服务块组成,每个服务块的形式如下(也可以把服务块放在/etc/xinetd.d目录下的的不同文件中);服务块的格式如下service服务名称服务相关配置。

m0_49864110的博客 979

W2 D3-进程相关、管道、文件查找

这里写目录标题一、软件包管理软件包管理概述二、进程管理1、**进程的基本概念**2、查看进程静态查看:**ps**动态top:查看、管理进程 一、软件包管理 软件包管理概述 安装/查询/卸载 sudo apt-get [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VbqxKaA-1626231207977)(C:\Users\10101\AppData\Roaming\Typora\typora-user-images\image-20210714094137439.png

十七阿哥的博客 561

3.进程管理相关命令

linux进程管理相关命令

qq_26511221的博客 111

Python3中多进程和多线程的理论相关

Python3中多进程和多线程的理论相关 作者:艾胖胖胖 链接:https://www.jianshu.com/p/b07f9f83142e 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 一、多任务 之所以需要使用多进程或者是多线程,归根结底就是为了进行多任务的处理。那么,多核CPU实现多任务原理和单核CPU的多任务原理是什么呢。 单核CPU的多任务原理 操作系统轮流让各个任务交替执行。比如:QQ执行2毫秒,微信执行2毫秒…任务不断反复切换,但又因为CPU调度执行速度太快

一颗糖的博客 242

进程相关知识系列(3 Android多进程通信基础概念)

多进程通信基础概念 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。简单来说,“序列化”就是将运行时的对象状态转换成二进制,然后保存到流、内存或者通过网络传输给其他端。 1 Seri...

lwj_hewo的博客 193
上一篇: 2.Makefile
下一篇: 4.进程相关2
许菠萝
博客等级 码龄10年 263粉丝 77原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值