【Linux】进程控制

  1. 进程创建fork/vfork

1.1.fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
//返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝至子进程 添加子进程到系统进程列表当中 fork返回,开始调度器调度
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程,看如下程序。
#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
    
    
int main()    
{    
    //printf("test makefile\n");    
    pid_t ret;    
    printf("Before: pid is %d\n", getpid());    
    ret = fork();    
    if(ret== -1){    
        perror("fork:");    
        exit(1);    
    }    
    else{    
        printf("after: pid is %d ; return is %d\n", getpid(),ret);    
    }    
    
    return 0;    
} 
这里打印了一个before,但是打印了两次after。
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器 决定。

结论

子进程和父进程是以写时拷贝的方式保证进程的独立性的。代码一般公用。可以通过判断fork返回值,来让子进程和父进程执行不同的代码片段。

1.2.fork函数返回值

进程返回0, 父进程返回的是子进程的pid。

理解要给父进程分会子进程的pid,给子进程返回0?

fork()的实现在,操作系统内核空间中,是一个存在于在内核空间的一个函数。
fork函数,在内部完成1.创建子进程pcb,2.赋值,3.创建子进程地址空间,4.赋值,5.设置子进程页表,6.将子进程pcb放入进程队列中------ ,最后返回 pid。
当走到返回的时候核心代码已经执行完了,已经是两个执行流了,子进程已经被创建了出来,并且执行了return pid指令。
返回的本质就是写入,所以谁先返回,谁就写入ret ,因为进程具有独立性,后返回的那个会发生写时拷贝。
所以同一个ret,地址是一样的,但是内容却不一样。

1.3.写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副 本。

具体见下图:

1.4.fork常规用法

一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.5.fork调用失败的原因

系统中有太多的进程
//这个代码可以测试此机器,能创建多少进程。
int main()
{
    int cnt =0;
    while(1){
        cnt++;
        int ret = fork();
        if(ret==0){while(1);}//让子进程一直循环等待。
        if(ret <0){printf("%d",cnt);}
    }
}
实际用户的进程数超过了限制
  1. 进程终止

2.1.main函数的返回值

以前我们在写c/c++程序的的时候,都会首先写main函数,最后都会 “ return0;"
这个“ return 0 ”就是进程退出的时候,对应的退出码。
退出码用来标定执行的结果是否正确。
int main()    
{    
    int a =1,b=2;    
    if((a+b) == 3)    
        return 0;    
    else    
        return 1;    
}
当我们的程序跑完了,可以查看退出码知道是否执行正确。

2.2.查看进程退出码

可以通过 echo $? 查看进程退出码
? : 是shell的一个变量,永远记录最近一个进程咋在命名行中执行完毕时对应的退出码
(main->return ? ;)
echo $?:是查看这个变量的值
为什么下面是 0 ?
因为echo也是一个进程。

2.3.如何设置进程退出码

如果一个进程不关心进程退出码,直接return 0 即可
如果未来我们需要关系进程退出码的时候,就返回特点数据表明特点的错误。
一般用 0 表示成功,用非0表示错误。
用不同的非0数字表示不同的错误。数字对人不友好,对计算机很友好。
一般而言,退出码都有对应的退出码文字描述。
这里的文字描述,1.可以自定义,2.可以使用系统当中的映射关系(不太使用)

strerror(int error):查看系统退出码的系统映射关系。

系统指令 都遵循系统给出的那一套退出码对应的退出信息。

2.4.进程退出的情况

代码运行完毕,结果正确 。return ->0
代码跑完了,结果不正确。return -> !0 //退出码这个时候起作用
代码没跑完,程序异常了。 //退出码无意义。

程序如何退出?

1.main函数return 返回。
2.任意地方调用exit();

exit和return的区别。

return:函数返回
exit:进程终止

_exit 和exit 的区别

exit是库函数
_eixt是系统调用
eixt底层调用了_exit
库函数是在系统调用接口之上的,exit是对_exit的封装。
exit会在进程终止前做一些其他事情,例如:刷新缓冲区.....
从这里可以看出我们以前语言级别的缓冲区是用户级别的缓冲区(在用户空间),是在系统调用之上的,一般系统调用不会刷新,用户级的缓冲区。库函数才能刷新用户级别的缓冲区。(后面基础IO文章会写到)

exit函数

exit最后也会调用exit, 但在调用exit之前,还做了其他工作。
1.执行用户通过 atexit或on_exit定义的清理函数
2.关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit

2.5.常见的进程退出方式

正常退出

1. 从main返回
2. 调用exit
3. _exit

异常退出

ctrl + c,信号终止
kill -9 ,杀死进程

_exit函数

#include <unistd.h>
void _exit(int status);
//参数:status 定义了进程的终止状态,父进程通过wait来获取该值
//说明:虽然status是int,但是仅有低8位可以被父进程所用。
//所以_exit(-1)时,在终端执行$?发现返回值是255。

exit函数

#include <unistd.h>
void exit(int status);

return退出

return是一种更常见的退出进程方法。在main函数中执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

  1. 进程等待

3.1.进程等待的介绍

之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对, 或者是否正常退出
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

案例1:wait回收子进程资源

案例2:waitpid获取子进程退出信息

3.2.进程等待的方法

3.2.1.wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);//相当于waitpid(-1,&status,0);
子进程没有退出的时候会一直等着子进程退出.
返回值:
 成功返回被等待进程pid,失败返回-1。
参数:
 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
 status子进程退出信息的位图.

3.2.2.waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
 当正常返回的时候waitpid返回收集(等待)到的子进程的进程PID;
 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
 pid:
 Pid=-1,等待任一个子进程。与wait等效。
 Pid>0.等待其进程ID与pid相等的子进程。
 status:
 WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
 WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
 options://可以控制阻塞等待和非阻塞等待
 WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
WNOHANG:非阻塞等待.
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回

3.3.子进程退出信息(status)

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待.

具体细节如下图(status有32个比特位,只研究status低16比特位):

终止信号表示程序是否正常退出,如果终止信号位0,表示程序正常退出,如果终止信号不为0,程序异常结束.退出码位置(次低8位)未使用.信号的不同就是错误的不同.

正常退出:

异常退出1:操作系统给进程终止信号

异常退出2:自己给进程传入终止信号

查看status的宏

查看status里面的子进程终止信号和子进程退出结果的时候,可以使用位运算的方式.
终止信号:(status&0x7F) , 退出码:(status>>8)&0xFF

也可以使用定义的一些宏来实现.

WIFEXITED(status) 如果子进程正常结束则为非0值。
WEXITSTATUS(status) 取得子进程退出码代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status) 如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程中止信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status)取得引发子进程暂停的信号代码,

查看所有的终止信号:

kill -l :罗列出所以的退出信号.

僵尸进程的status的信息存放在哪里呢?

子进程在僵尸状态后,可以理解为他的代码和数据可以被os释放掉,但是退出信息一定要保存下来供父进程查看.
子进程的退出信息保存在子进程的PCB中
父进程通过wait/waitpid ,获取子进程的退出信息,让子进程进入死亡状态.
所以等待的本质就是,检测子进程的退出信息,将子进程退出信息通过status拿回来.
wait/waitpid的本质就是,os去检测子进程PCB中退出信息,将子进程填写带status中.
进程退出会进入僵尸状态, 会把自己的退出信息写到自己的PCB中,
wait和waitpid是一个系统调用,os有资格去读取子进程的PCB.从而拿到子进程的退出信息.

3.4.阻塞等待和非阻塞等待

阻塞等待

非阻塞等待

非阻塞等待不会占用,父进程全部的时间,它可以在轮询的时候执行别的代码.
  1. 进程替换

4.1.进程替换的目的

a.想让子进程执行父进程代码的一部分(子进程执行父进程磁盘中代码的一部分)。
b.想让子进程执行一个全新的程序(让子进程想办法重新磁盘上指定的程序,执行新的代码和数据)。

调用系统的程序

调用自己的程序

//第一个参数是相对路劲或者绝对路劲

4.2.替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。 当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec并不创建新进程,所以调用exec前后该进程的id并不改变

父进程直接替换

通过子进程替换

这就是为什么刚刚运行的原程序第二个Printf并未执行。
exec是一个函数,只要是函数就有可能调用失败,就是没有替换成功,就是没有替换。
成功就不会执行exec函数以后的代码,出错了就是没有替换,还会执行exec函数以后的代码。
这里的exec函数只有在出错的时候才有返回值。
因为进程具有独立性,所以一般都是创建子进程后再进程函数替换,这样子进程不会影响父进程的执行。

4.3.替换函数

C语言库中的6个函数替换

这些都是用系统调用封装的函数

库函数execl

库函数execlp

库函数execle

上面我们发现,我们导入自己的环境变量就不能使用系统的环境变量,使用系统的环境变量就不能使用我们自己定义的环境变量。

这个时候需要putenv

putenv("MYENV=xxxxxx");将指定的变量导入到系统的环境变量表中。

库函数execv

库函数execvp

库函数execvpe

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。

4.4.命名理解

库函数exec

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

l(list) : 表示参数采用列表(参数一个一个传入)
v(vector) : 参数用数组(以数组指针的方式传入)
p(path) : 有p自动搜索环境变量PATH(不用告诉我具体路劲)
e(env) : 表示自己维护环境变量(自己传入环境变量)

系统调用execve

事实上,只有execve是真正的系统调用,其它六个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在
man手册第3节。

这些函数之间的关系如下图所示.

我们的程序,要执行必须要加载到内存,如何加载呢?
Linux exec* 系列的函数,也叫加载器.

4.5.替换其他语言的程序

首先我们知道肯定可以替换C语言的程序,系统的命令就是C语言写的。

也可以替换别的语言比如:

c++语言,python,shell,Java

程序替换,可以使用程序替换,替换掉任何后端语言对应的可执行程序。

  1. 模拟实现shell程序

5.1.思路

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束

所以要写一个shell,需要循环以下过程:

1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)

根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。

5.2.代码实现

#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<string.h>
#include<assert.h>                                                                                                                                                                           


#define NUM 1024
char linecommand[NUM];
char* argv_[60];
int lastcode =0;
int lastsig = 0;

int main()
{
    while(1){
//打印提示符
        printf("用户名@主机名 当前路劲#");
        fflush(stdout);
//获取用户输入
        char* str = fgets(linecommand, NUM-1, stdin);
        assert(str != NULL);
        (void)str;
        //清楚最后一个\n的字符,
        //因为我们输入完成后会敲入一个回车键,回车也会被获取到linecommand中
        linecommand[strlen(linecommand)-1] = '\0';//消除最后的回车键
        //printf("%s\n",linecommand);//获取成功
        
//分割linecommand,
        argv_[0] = strtok(linecommand," ");
        int i = 1;
        //处理ls的颜色和缩写
        if(strcmp(argv_[0],"ll") == 0){argv_[0] = (char*)"ls";argv_[i++] = (char*)"-l";}
        if(strcmp(argv_[0],"ls") == 0){argv_[i++] = (char*)"--color=auto";}
        while((argv_[i++] = strtok(NULL, " "))!=NULL);
        
        //处理cd 和 echo这样的内建命令
        if(argv_[0] != NULL && strcmp(argv_[0], "cd")== 0){
            if(argv_[1] != NULL)chdir(argv_[1]);
            continue;
        }
        if(argv_[0] != NULL && argv_[1] != NULL && strcmp(argv_[0], "echo")==0){
            if(strcmp(argv_[1], "$?")==0)
                printf("code:%d sig:%d\n", lastcode,lastsig);
            else
                printf("%s\n",argv_[1]);
            continue;
        }

//创建子进程,进行程序替换
        pid_t id = fork();
        assert(id != -1);
        if(id == 0 )
        {
            execvp(argv_[0], argv_);
            exit(1);
        }

        int status = 0;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        assert(ret > 0);
        (void)ret ;
        lastcode = ((status>>8)&0xff);
        lastsig = (status&0x7f);

    }
} 

chdir:修改程序运行地址

假如我们不用chdir处理cd ,我们无论怎么cd, 当再次去执行pwd的时候都是myshell所在的目录。是因为cd只是修改了子进程的运行目录,修改完子进程的运行目录后子进程就没有了,退出了,父进程还是在myshell运行目录下,所以cd命令不需要,创建子进程,直接修改myshell进程的运行目录即可。chdir就可以修改。
内建命令也解释了,为什么echo 可以打印,本地变量。也可以打印环境变量。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端实现一个名言生成器

The sand accumulates to form a pagoda✨ 写在前面✨ JS是什么&#xff1f;✨ 名言生成器✨ 页面搭建✨ 功能实现✨ 写在前面 在上周我们通过HTML、CSS实现了一个简单的‘我的相册‘页面的搭建&#xff0c;很多伙伴呢跟我说难道前端就只能做一些页面搭建的工作吗&#xff1f;…

Linux系统编程 - 基础IO(IO操作)

目录 预备知识 复习C文件IO相关操作 printf相关函数 fprintf snprintf 读取文件 系统文件IO操作 open函数 umask()函数 open函数返回值 预备知识 1.你真的理解文件原理和操作了吗&#xff1f;不是语言问题&#xff0c;是系统问题2.是不是只有C/C有文件操作呢&#x…

【Java开发】设计模式 08:组合模式

1 组合模式介绍组合模式是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构&#xff0c;以表示部分-整体的层次结构。组合模式使得客户端可以统一处理单个对象和组合对象&#xff0c;从而简化了客户端代码。在组合模式中&#xff0c;有两种类型的对象&#xff1a;叶子…

【C语言初阶】函数

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;函数是什么&#xff1f;&#x1f337;函数的分类&#x1f33a;库函数&#x1f33a;自定义函数&#x1f337;函数的参数&#x1f337;函数的调用&#x1f337;函数的嵌套调用和链式访问&#x1f33a;嵌套调用&a…

小游戏也要讲信用

当下&#xff0c;小游戏鱼龙混杂&#xff0c;官方为能更好地保护用户、开发者以及平台的权益&#xff0c;近日宣布7月1日起试行小游戏主体信用分机制。 主体信用分是什么呢&#xff1f;简单来说&#xff0c;这是针对小游戏主体下所有小游戏帐号行为&#xff0c;对开发者进行评…

深度学习中的学习率设置技巧与实现详解

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

(五)Tomcat源码阅读:Engine组件分析

一、概述 在阅读源码之前我们需要对各个类的关系有一个清晰的了解&#xff0c;下面就是Engine各个类之间的关系&#xff0c;我们将会按照从上到下的顺序阅读源码。 二、阅读源码 1、Container &#xff08;1&#xff09;注释 Container可以处理请求并给予相应&#xff0c;并…

JavaScript-扫盲

文章目录1. 前言2. 第一个 JavaScript 程序3. javaScript 的基础语法3.1 变量3.2 数据类型3.3 运算符3.4 条件语句3.5 数组3.6 函数3.7 作用域3.8 对象4. WebAPI4.1 DOM 基本概念4.2 常用 DOM API4.3 事件4.4 操作元素4.5 网页版猜数字游戏4.6 留言版1. 前言 提问 java 和 java…

集合之CurrentHashMap 1.7总结

文章目录底层实现构造方法默认的三个参数什么是Unsafe类&#xff1f;它有什么作用&#xff1f;为什么CurrentHashMap 调用Unsafe方法不会报错&#xff1f;我们自己创建的对象调用会报错&#xff1f;CurrentHashMap的key&#xff0c;value可以为null吗&#xff1f;CurrentHashMa…

水风险指数定义及计算:水资源压力等

水风险指数&#xff08;Water risk indicators&#xff09; 水风险指数&#xff08;Water risk indicators&#xff09;是用来评估水资源可持续性和水相关风险的一种工具&#xff0c;可以通过多种指标来衡量。 1.1 水资源压力&#xff08;water stress, WS&#xff09; 定义…

leetcode -- 142. 环形链表 II

&#x1f428;目录&#x1f4dc;1. 题目&#x1f50d;2. 思路&#x1f511;2.1 链表是否带环&#x1f511;2.2 为何能追上&#x1f511;2.3 入口点的确定&#x1f513;3. 代码实现&#x1f4e1;4. 题目链接&#x1f4dc;1. 题目 给定一个链表的头节点 head&#xff0c;返回链表…

自定义类型 (位段、枚举、联合体)

文章目录&#x1f4ec;位段&#x1f50e;1.什么是位段&#x1f50e;2.位段的内存分配&#x1f50e;3.位段的跨平台问题&#x1f4ec;枚举&#x1f50e;1.枚举类型的定义&#x1f50e;2.枚举的优点&#x1f50e;3.枚举的使用&#x1f4ec;联合&#xff08;共用体&#xff09;&am…

C/C++中for语句循环用法及练习

目录 语法 下面是 for 循环的控制流&#xff1a; 实例 基于范围的for循环(C11) 随堂笔记&#xff01; C语言训练-计算1~N之间所有奇数之和 题目描述 输入格式 输出格式 样例输入 样例输出 环形方阵 干货直达 for 循环允许您编写一个执行特定次数的循环的重复控制结构。…

Go语言基础:数组定义及循环遍历

前言 大家好&#xff0c;我是沐风晓月&#xff0c;本文go语言入门-掌握go语言函数收录于《go语言学习专栏》专栏&#xff0c;此专栏带你从零开始学习go语言&#xff0c;持续更新中&#xff0c;欢迎点赞收藏。 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人…

Postman接口与压力测试实例

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。它提供功能强大的 Web API & HTTP 请求调试。 1、环境变量和全局变量设置 环境变量可以使用在以下地方&#xff1a; URLURL paramsHeader valuesform-data/url-encoded valuesRaw body contentHelper fi…

RedgateClone启用并行工作流 crack

RedgateClone启用并行工作流 crack RedgateClone允许您轻松创建用于开发和测试场景的数据库的一次性副本。通过使用生产数据库的克隆来降低成本并提高测试和发布的质量。Redgate克隆支持Microsoft SQL Server、Postgres、Oracle和MySQL数据库。 红门克隆的好处 最多可节省99%的…

CentOS从gcc 4.8.5 升级到gcc 8.3.1

gcc -v查看当前gcc版本。 sudo yum install centos-release-scl-rh安装centos-release-scl-rh。 sudo yum install devtoolset-8-build安装devtoolset-8-build。 显示“Complete!”表示安装成功。 sudo yum install devtoolset-8-gdb安装devtoolset-8-gdb。 显示“Comple…

[JAVA]一步接一步的一起开发-图书管理系统(非常仔细,你一定能看懂)[1W字+]

目录 1.想法 2.框架的搭构 2.1图书 2.1.1Book类 2.1.2BookList类 2.2用户 2.2.1User抽象类 2.2.2AdminUser类&#xff08;管理者&#xff09; 2.2.3NormalUser 2.3操作 操作接口 借阅操作 删除操作 查询操作 归还图书 展示图书 退出系统 2.4小结 3.主函数的编…

【python实操】年轻人,别用记事本保存数据了,试试数据库吧

为什么用数据库&#xff1f; 数据库比记事本强在哪&#xff1f; 答案很明显&#xff0c;你的文件很多时候都只能被一个人打开&#xff0c;不能被重复打开。当有几百万数据的时候&#xff0c;你如何去查询操作数据&#xff0c;速度上要快&#xff0c;看起来要清晰直接 数据库比我…

Azure OpenAI 官方指南03|DALL-E 的图像生成功能与安全过滤机制

2021年1月&#xff0c;OpenAI 推出 DALL-E。这是 GPT 模型在图像生成方面的人工智能应用。其名称来源于著名画家、艺术家萨尔瓦多 • 达利&#xff08;Dal&#xff09;和机器人总动员&#xff08;Wall-E&#xff09;。DALL-E 图像生成器&#xff0c;能够直接根据文本描述生成多…