深度探索Linux操作系统 —— 构建桌面环境1

简介: 深度探索Linux操作系统 —— 构建桌面环境

前言

   计算机领域中的桌面环境(Desktop Environment)其实是一种比喻的说法,即图形用户界面就像物理书桌一样,其上可以放置文件夹、文档等。桌面最初用来特指个人计算机(PC),但是现在不只个人计算机有图形界面环境,服务器、嵌入式设备等基本都提供桌面环境。桌面环境包括窗口管理器、任务条等基本组件,除了这些基本的组件外,有的桌面环境还提供文件管理器、控制面板等。


   桌面环境是操作系统中人机交互的关键部分,理解它的基本运作原理,无论是对理解操作系统,还是对开发应用程序,都有极大的帮助。我们处于这样一个追求个性的年代,无论是用于消费类电子设备的移动系统,还是用于 PC 的中规中矩的桌面系统,人们都已不再满足于千篇一律的桌面。打造一个全新的个性化桌面,绝不只是停留在更改个背景图、换个主题这个层面,我们需要更大的革新。但是如果对桌面环境的基本原理都不甚了解,那又何谈去开发打造具有创造性的用户交互。


   因此,在本章中我们带领读者从头构建一个基本的桌面环境,包括窗口管理器、任务条以及一个显示桌面背景的组件。为了使读者更能深刻体会 X 的客户/服务器模型,窗口管理器基于 Xlib 编写,而任务条等组件则展示了使用 GTK 图形库的编程方法。


   限于篇幅,我们没有将全部源代码全部贴到书中,所以请读者结合随书光盘中附带的源代码进行阅读。另外,本章虽然涉及 Xlib 和 GTK 编程,但是为了不干扰主线 —— 构建桌面环境,我们不会过多讨论它们的编程,其中涉及的 API ,如有必要请参考 Xlib 和 GTK 各自的参考手册。


一、窗口管理器

   本质上,窗口就是显示器上对应的一块区域。对于一个运行多任务的操作系统来讲,在一个有限的屏幕上可以同时存在多个窗口,因此,用户希望多个窗口之间可以协调布局和平共享同一个屏幕。可以将特定窗口切换为当前活动窗口;可以按需改变窗口尺寸;可以最大化、最小化以及关闭窗口。但是 X 的设计哲学是只提供机制,不提供策略,X 服务器只提供窗口操作相关的函数,但不管如何去操作窗口。于是诞生了另外一个特殊的 X 应用:窗口管理器。


1、基本原理

1. X 的窗口

   X 将所有窗口组织为一棵树。X 服务器启动后,将默认创建一个窗口,这个窗口充满整个屏幕,作为整个窗口树的根,称为根窗口(Root Window),所有应用的顶层窗口(Top-level Window)都是根窗口的子窗口。


   假设在 X 中运行两个应用 A 和 B ,A 包含 2 个窗口,B 应包含 3 个窗口,窗口之间的布局如图 7-1 所示。

beb95fb7076ce27143bd539e11a385ea.png



它们之间的树形关系如图 7-2 所示。

0078765fb059a26f3fadbd14c0dbf664.png



   窗口管理器仅管理应用的顶层窗口,即如图 7-2 中的 “Top Window A” 和 “Top Window B” 。一个应用可能有多个顶层窗口,除了应用的主窗口之外,对话框一般也是一个顶层窗口。而对于顶层窗口的子窗口,则由应用自己管理。


2. 窗口装饰

   在第 6 章中,我们看到,无论是基于 Xlib 的程序,还是使用 GTK 编写的程序,在没有窗口管理器的情况下,它们的窗口都以 “素颜” 示人,只是一个 “裸” 窗口。一个典型的桌面应用的窗口,一般而言,包括一个标题栏,标题栏上还可能显示窗口的名称、最大化、最小化和关闭按钮。另外,窗口一般还有一个边框。用户可以通过标题栏移动窗口,可以在边框处拖动鼠标改变窗口尺寸,可以分别通过最大化、最小化和关闭按钮最大化、最小化、关闭窗口。这些组件除了具备功能外,还具备美化的作用,比如可以设置窗口边框的颜色、阴影效果等,因此,它们也被称为窗口装饰。


   显然,窗口装饰不应该由各个应用负责,暂且不提重复劳动,单单一致性就是个大问题。如果任由应用自己绘制,最后将导致窗口标题栏等装饰五花八门。因此,在 X 中,将窗口装饰提取为公共部分,由窗口管理器统一负责。通常的实现方式是:窗口管理器创建一个窗口,我们称这个窗口为 Frame ,作为根窗口的子窗口,但是作为应用的顶层窗口的父窗口。其他装饰,或者直接绘制在 Frame 窗口上,或者创建新的装饰窗口,但是这些装饰窗口也作为 Frame 的子窗口,本章我们开发的窗口管理器采用后者。应用的顶层窗口和 Frame 窗口之间的关系如图 7-3 所示。

a9e9f4e8d07c07381e4cdea3197e542b.png



3. 拦截事件

   X 服务器维护一个事件队列,在该队列中按顺序保存着发生的各个事件,并周期地分发给应用。每个应用可以选择对发生在某些窗口上的哪些事件感兴趣,如果多个应用对同一个事件感兴趣,X 服务器将复制该事件的多个副本,并将其分发给各个对其感兴趣的应用,如图 7-4 所示。

ec2319e1d63b0b953e5c2c0d13717c7c.png



   Xlib 提供了函数 XSelectInput,应用程序可以使用该函数选择接收指定窗口的事件,其函数原型如下:

XSelectInput(Display *display, Window w, long event_mask)

   其中参数 w 表示接收发生在窗口 w 上的事件,event_mask 表示对哪些事件感兴趣,如 ButtonPressMask 表示希望接收窗口 w 的 ButtonPress 事件。


   在这些事件掩码中,有一个比较特殊 —— SubstructureRedirectMask,其含义是:当某个应用选定了某个窗口的 SubstructureRedirectMask 时,该窗口的子窗口( Substructure ) 发送给 X 服务器的 MapRequest 、 ConfigureRequest 和 CirculateRequest 三类请求,都将被重定向给这个应用,这就是 X 的 “Substructure Redirection” 机制。


   窗口管理器恰恰利用了这个机制,对根窗口选择了 SubstructureRedirectMask ,从而截获了应用的顶层窗口的请求。其中最关键的是 MapRequest ,在窗口请求 X 服务器显示时,其将向 X 服务器发送 MapRequest 请求。在截获了 MapRequest 后,窗口管理器创建 Frame 窗口,作为根窗口的子窗口,然后暗渡陈仓,将应用的顶层窗口从根窗口脱离,而将其作为 Frame 窗口的子窗口,同时也创建其他窗口装饰。都伪装好后,窗口管理器再以 Frame 窗口的身份,请求 X 服务器显示 Frame 窗口。应用的顶层窗口作为 Frame 窗口的子窗口,当 Frame 窗口得以显示后,其自然也被显示。在某种意义上,窗口管理器通过 Frame 窗口控制了应用的顶层窗口,从而达到管理它们的目的。


   在应用的顶层窗口作为 Frame 窗口的子窗口后,窗口管理器还是要关心它们发送给 X 服务器与窗口管理相关的请求,因此,如同设置根窗口的 SubstructureRedirectMask ,窗口管理器也需要设置 Frame 窗口的 SubstructureRedirectMask。


   不知读者是否考虑过这样一个问题:既然 X 服务器将其他应用的 MapRequest 请求重定向给窗口管理器,那么窗口管理器同样也作为 X 服务器的一个客户程序,它也需要向 X 服务器发送 MapRequest 请求,比如请求显示 Frame 等装饰窗口。如此这般,X 服务器岂不是将窗口管理器发送给它的请求再重定向给窗口管理器?如此往复,岂不是形成了死循环?


   为此,窗口提供了一个属性:override_redirect 。如果窗口的这个属性值为 True,则其明确告知 X 服务器自己不需要窗口管理器的管理,X 服务器就不会将这个窗口的请求重定向给窗口管理器。我们常用的鼠标右键菜单就是一个典型的将属性 override_redirect 设置为 True 的窗口。因此,窗口管理器在创建 Frame 等装饰窗口时,可以通过将它们的这个属性设置为 True 来解决我们刚刚谈到的死循环问题。事实上,即使不设置这个属性,也不会形成死循环,X 的开发者已经考虑了这个问题。


   窗口管理器除了关心应用的顶层窗口的 SubstructureRedirectMask 涉及的请求外,另外还要获得它们的某些通知事件。其中一个就是 UnmapNotify ,在收到这个通知后,窗口管理器需要清理所有为该窗口创建的对象,包括窗口装饰等。所以除了事件掩码 SubstructureRedirectMask 外,窗口管理器还要选择根窗口和 Frame 窗口的事件掩码 SubstructureNotifyMask 。


4. 窗口间通信

   在一个标准的桌面环境下,存在多个不同的应用程序,除了普通的应用程序外,还有构成基本桌面环境的组件,如任务条等。而且,每个应用的窗口布局策略不尽相同,比如普通的 X 应用一般带有窗口装饰,但是我们有看到过构成桌面环境的任务条装饰着标题栏,并且标题栏上有最大化/最小化以及关闭等按钮吗?显然,这类组件不需要窗口装饰。我们还以任务条为例,在某些桌面环境上,任务条可以放置在屏幕的上方、下方、左侧以及右侧。再比如,对话框的窗口装饰中通常是没有最大化按钮的。


   显然,窗口管理器需要获得窗口的相关信息,才能根据这些信息决定如何为这些窗口在同一个屏幕上协调的布局以及如何装饰这些窗口。为此,X 提供了多种窗口间通信的机制,属性(Property)是窗口管理器和应用的窗口之间使用的主要通信机制。


   X 默认定义了一些属性,这些属性在窗口管理器规范中约定,但是应用也可以自定义属性。在 X 中,每个窗口都附着一个属性表,表中每一行大致就是属性的名字和其对应的值。应用可以设置自己创建的窗口的属性,也可以读取或者改变其他应用的窗口的属性,从而达到不同窗口间通信的目的。


   属性保存在 X 服务器端。每个属性都有一个名字,为了便于使用属性,属性的名字是可读性更好的 ASCII 字符串而不是一串数字。然而,如果应用程序使用属性的名字引用属性,势必要通过套接字传递属性的名字给 X 服务器。但是字符串的数据量明显大于一个固定长度的整数,而且,还有一点,字符串的长度是可变的,也给协议的实现增加了复杂度。为此,X 又为每个属性起了个小名,这个小名是一个整型数,与属性的名字间是一一对应的关系,X 将其称为 Atom ,在应用与服务器之间通信时,使用这个小名而不是可变长度的字符串。


   属性对应的 Atom 是动态创建的,当 X 服务器启动时,会为一些属性创建 Atom , 其他则是在首次使用时创建。 Xlib 提供了函数 XInternAtoms 和 XInternAtom 用来获取属性名对应的 Atom 。这两个函数基本相同,只不过一个是 “批发” ,一个是 “零售” ,相对于 XInternAtom 而言,XInternAtoms 减少了应用和服务器之间的通信次数。XInternAtoms 函数原型如下:


Status XInternAtoms(Display *display, char **names, int count, 
    Bool only_if_exists, Atom *atoms_return);

   其中,参数 names 包含要转换的属性的名称,count 表示转换的数量,转换后的 Atom 存储在数组 atoms_return 中。如果属性的 Atom 已经存在了,则直接获取其值即可,否则,是否为属性创建 Atom 要根据参数 only_if_exists 的值而定。只有 only_if_exists 为 False 时,才创建 Atom 。


   Xlib 提供了函数 XGetWindowProperty 和 XChangeProperty 来读写窗口的属性,我们以 XGetWindowProperty 为例来讨论一下如何读取窗口属性。


int XGetWindowProperty(Display *display, Window w, Atom property,
  long long_offset, long long_length, Bool delete,
  Atom req_type, Atom actual_type_return,
  int *actual_format_return, unsigned long *nitems_return,
  unsigned long *bytes_after_return,
  unsigned char **prop_return);

   1)参数 property 指的就是准备读取的窗口 w 的属性,根据该参数类型也印证了 X 没有使用属性的名字,而是使用了占用字节数更少的属性的 Atom 。


   2)属性的值可能是一个数组,比如窗口管理器规范 EWMH 规定属性 _NET_WM_WINDOW_TYPE 值就是一个 Atom 数组。数组就是在内存中的一块缓冲区了,从这个角度,就比较容易理解参数 long_offset 和 long_length 的意义了。XGetWindowProperty 为获取窗口属性提供了更大的灵活性,调用者可以通过参数 long_offset 和 long_length 读取存储属性值的缓冲区中指定偏移处的指定长度的值,这两个参数均以 32 位为单位。


   3)在读取窗口的属性后,可以通过参数 delete 告诉 X 服务器是否删除窗口的这个属性,这也是为了节省内存空间考虑。


   4)XGetWindowProperty 允许调用者传递参数 req_type 告诉服务器读取的属性值的类型,典型的包括 XA_ATOM、XA_CARDINAL 以及 XA_STRING 等,分别表示属性的值为 Atom、32 位整数以及字符串类型。当不确定属性的值的类型时,可以传递 AnyPropertyType 给 X 服务器,由 X 服务器将实际的类型通过参数 actual_type_return 返回给应用程序。


   5)XGetWindowProperty 收到 X 服务器的返回值后,将动态申请一块内存,保存读取到的属性的值,并使用指针 prop_return 指向这块内存。既然是动态申请的内存,使用后需要用 Xlib 的函数 XFree 将其释放。


   6 ) XGetWindowProperty 将实际读取的属性的值的类型保存在 actual_type_return 中;将实际读取的属性的值的格式保存在 actual_format_return 中,属性的值的格式可以是 8、16 或 32 三者之一,分别代表 char、short 以及 long ;如果读取操作仅读取了保存属性值的缓冲区中的部分数据,则 XGetWindowProperty 将保存属性值的缓冲区中剩余的尚未读取的字节数存储在 bytes_after_return 中;nitems_return 中记录的是实际读取的属性的数量。


5. 捕捉窗口

   我们设想这样一种场景,如图 7-5 所示,假设 X 服务器上已经在运行两个 X 应用 A 和 B ,A 是当前活动的应用,B 是非活动应用。B 有两个顶层窗口,除了主窗口外,打开文件对话框也是一个顶层窗口,同时这个对话框也是应用 B 的临时(transient)窗口。正如其字面意义所言,所谓的 “transient” 就是临时的、短暂的,是一个相对的概念,是相对于某一窗口而言的。举个例子,如某些应用的 “打开文件” 对话框,是一个典型的临时窗口。但是如果某个应用的主窗口就是一个对话框,那么这个对话框就不是临时窗口了。


561fa1b34f82efa8669693d812a958e1.png


   当用户想要将应用 B 切换为当前活动的应用时,常用的方法之一是使用鼠标点击 B 应用的窗口。这时窗口管理器拦截鼠标事件,然后请求 X 服务器重新排列窗口栈序,具体细节见 7.1.11 节。总之窗口管理器必须要能接收到鼠标事件,如果接收不到鼠标事件,一切都无从谈起。


   Frame 等装饰窗口是窗口管理器创建的,因此窗口管理器可以自如控制,比如我们可以设置 Frame 窗口的事件掩码中包含 ButtonPressMask 。而对于应用的顶层窗口,我们肯定不能过多干涉。但是,我们又不能强制用户一定要点击到 Frame 窗口上未被应用顶层窗口覆盖的地方。而且一般情况下,用户一定是点击到顶层窗口或者其子窗口上,而不是 Frame 窗口上,毕竟 Frame 窗口未被应用顶层窗口遮挡的区域除了标题栏外,只有很小的边框了,也就是说能被点击到的区域很小。


   根据 X 的事件传播机制,如果发生在一个窗口上的事件未被处理,在该窗口没有设置禁止事件继续向其父窗口传播的情况下,事件将沿着窗口树一直向着树的根部传播。很少有具有图形界面的程序不处理鼠标事件,否则就没有任何意义了,也就是说,鼠标事件几乎永远传递不到 Frame 窗口,都被应用自身消化了。如果不能接收鼠标事件,更何谈激活窗口了。那么怎么解决这个问题呢?


   X 提供了鼠标捕捉机制,其又分为主动捕捉和被动捕捉。以图 7-5 为例,假设另外一个应用以被动机制捕捉应用 B 的顶层窗口时,当用户在应用 B 的顶层窗口范围内按下鼠标时,将激活捕捉机制,X 服务器将鼠标事件不再按照正常的事件传播路径传播了,而是转发给捕捉应用 B 的顶层窗口的 X 应用。窗口管理器恰恰是利用了这个机制,捕捉非活动窗口,从而捕获这些窗口的鼠标事件,实现不同应用间的切换。


   Xlib 提供的用于捕捉的函数是 XGrabButton ,其原型如下:


XGrabButton(Display *display, unsigned int button,
    unsigned int modifiers, Window grab_window, Bool owner_events,
    unsigned int event_mask, int pointer_mode,
    int keyboard_mode, Window confine_to, Cursor cursor);

   其中各个参数意义如下:


   ◆ button 表示捕捉鼠标哪个键,比如是捕捉左键还是捕捉右键等。


   ◆ modifiers 表示是否要求同时按下键盘上某个按键才能捕捉,也就是我们所说的修饰键。


   ◆ event_mask 表示捕捉事件的掩码,即捕捉什么事件,是捕捉按下鼠标事件还是捕捉释放鼠标事件等。


   ◆ confine_to 表示是否需捕捉的区域限制在某个范围,也就是说,当事件发生时,只有鼠标在这个区域才可以捕捉。


   ◆ cursor 表示当捕捉发生时,是否需要使用特定的鼠标指针形状,以给用户一个友好的提示。


   ◆ owner_events 主要是用于当应用捕捉自身创建的窗口时使用,与窗口管理器无关。


   ◆ 参数 grab_window 是最核心的一个参数,理解了这个参数就基本理解了整个函数,这个参数就是表明当鼠标按键发生在哪个窗口时进行捕捉。


   ◆ 最后来解释参数 pointer_mode 。我们举个例子来解释这个参数,假设我们将捕捉比喻为窃,那么捕捉其他窗口的应用就是江洋大盗,被捕捉的窗口所属的应用就是受害人。不知读者是否有这样的疑问:当江洋大盗将事件窃走后,受害人还能否失而复得。X 再次将这个策略性的问题抛给了应用自己来决定。X 提供了两种捕捉模式:异步模式和同步模式。当使用异步模式时,受害人不要心存任何侥幸了。而当使用同步模式时,在取消对一个窗口的捕捉行为后,如果江洋大盗良心发现,X 则会给他一次浪子回头的机会。江洋大盗可以调用 Xlib 的函数 XAllowEvents 放行这个被截获的事件,这样受害者就可以失而复得了,但是可能不是那么新鲜了,要晚一点。


6. save-set

   笔者没有找到一个恰当一点的词来表达 save-set 这个术语,所以我们就直接用英文了。根据其名字就可以猜出这是一个集合了。但是这个集合是做什么的呢?


   我们设想这样一种情况,当窗口管理器异常终止时,窗口管理器创建的 Frame 等装饰窗口自然也被销毁。销毁这些窗口本身没有问题,但是它们带来了副作用:作为 Frame 窗口子窗口的应用的窗口也被销毁。这显然不是我们希望看到的。


   每个 X 应用都有一个 save-set ,其中保存的就是就是窗口的列表。当应用异常断开到 X 服务器的连接时,X 服务器将首先检查应用的 save-set ,并安排根窗口领养 save-set 中的窗口,从而避免了在 save-set 中的这些窗口被销毁。


   前面提到的窗口管理器的问题恰恰可以用这个方法解决。每当管理一个窗口时,窗口管理器就可以调用 Xlib 的函数 XAddToSaveSet 将其加入到自己的 save-set 中。一旦当窗口管理器异常终止,根窗口将领养应用的窗口,从而避免了 Frame 窗口被销毁时,应用的窗口也被销毁的命运。


2、创建编译脚本

   开发者们开发了 GNU 构建系统(GNU Build System),或者叫 GNU 自动构建工具(GNU Autotools),为了行文方便,我们简称其为 Autotools 。Autotools 核心包括 Autoconf 和 Automake 。这里要准确理解 “自动构建工具” 的意义,所谓 Autotools,并不是自动完成整个配置编译过程,而是自动构建配置脚本 configure 和 Makefile 。


(1)Autoconf

   Autoconf 的准确含义是自动创建自动配置脚本( automatically create automatic configuration scripts)。怎么理解自动配置脚本呢?简单来讲,就是自动探测各种不同系统的各种特性,如是本地编译还是交叉编译,系统中使用的编译器、链接器等程序是什么,编译以及链接程序时需要的头文件、动态库以及它们所在的路径,等等,达到自动动态适配,而不是硬编码到脚本中。


   可以这样概括 Autoconf 的工作过程:将多个 shell 片段最终合并为一个完整的 shell 脚本,即 configure 。Autoconf 使用宏来定义这些 shell 片段,开发者需要根据编译需要,使用这些宏组合 Autoconf 的元文件 configure.ac ,这个元文件曾经命名为 configure.in ,后来更改为 configure.ac ,但是 Autoconf 也向后兼容 configure.in 。然后 Autoconf 将元文件 configure.ac 中的宏展开为具体的配置脚本 configure 。


   Autoconf 程序本身使用 shell 脚本编写,但是 Autoconf 并没有使用 shell 完成宏展开功能,而是借助了 GNU 的 M4 来完成宏的展开。简单来讲,M4 就是将输入的宏名转换为宏定义,也就是说,M4 的输入是宏名,而输出是 shell 脚本片段。 Autoconf 使用 M4 定义了一些内置的宏,并且基于 M4 之上又封装了一层宏,目的是为了更符合 Autoconf 的需求,Autoconf 封装的宏一般以 “AC_” 开头。其他程序可以使用 Autoconf 封装的这些宏,或者直接使用 M4 定义自己的宏,但是最终,本质上都是 M4 宏。


   因为 M4 宏定义很多是第三方程序提供的,可能安装在系统的多个位置,因此 GNU 自动构建系统编写了程序 aclocal 负责将这些宏定义收集到文件 aclocal.m4,保存在源码的顶层目录下,供自动构建系统使用。


(2)Automake

   同 Autoconf 类似, Automake 的准确含义是 “automatically generate makefile.in”,开发人员只需编写一个简单的元文件,在其中描述必要的诉求:比如构建一个二进制程序,使用的源代码文件是什么,链接某某库等即可。其他的都交由 Automake 全权处理吧。 Automake 将创建一个标准的 Makefile 文件,包括补全开发者不愿意编写的那些琐碎的规则,如 install、clean、distclean、dist 等。


   Automake 的输出事实上是一个 Makefile 模板,命名为 Makefile.in 。然后,configure 脚本使用探测到的值替换模板 Makefile.in 中的变量,创建最终的 Makefile 。显然,这种方式要比我们将所有的变量定义全部硬编码到 Makefile 中的做法可移植性更好。


   综上,使用 GNU Autotools 创建 Makefile 的过程可以分为如下几个步骤:


   1)编写元文件 configure.ac 。


   2)执行 aclocal 。aclocal 将扫描 configure.ac 中使用的 M4 宏,并到系统中收集这些宏的定义,然后将这些宏定义复制到源码顶层目录下的 aclocal.m4 中。


   3)调用 autoconf,将 configure.ac 中的宏展开为 shell 脚本形式的 configure 。


   4)编写元文件 Makefile.am 。


   5)调用 automake 。 automake 根据 Makefile.am 创建 Makefile 的模板文件 Makefile.in 。


   6)执行脚本 configure。configure 探测系统环境,并使用探测到的值替换模板 Makefile.in 中的变量,生成具体的 Makefile 。


   从上面的讨论可以看出,对于开发者来说,主要的工作就是创建元文件 configure.ac 和 Makefile.am ,其他的全部交给 Autotools 。Autotools 极大地减轻了程序开发人员的负担,将烦琐的编写的 Makefile 任务转嫁给了 Autotools 的开发和维护者。


   既然 Autotools 有如此多的优点,所以即使我们的迷你窗口管理器很小,我们还是可以借助它感同身受一下 Autotools 带来的好处。我们这里绝非 “杀鸡用牛刀”,而是希望读者借助这个例子,可以切身体会一下 Autotools ,这样无论是在大型项目中使用 Autotools ,或者为 GNU 软件贡献源码,亦或基于使用 Autotools 的项目进行二次开发,都会大有益处。

深度探索Linux操作系统 —— 构建桌面环境2:https://developer.aliyun.com/article/1598093


目录
相关文章
|
1天前
|
安全 Linux 网络安全
Linux环境中安装和使用Paramiko
Linux环境中安装和使用Paramiko
24 12
|
3天前
|
Linux 数据安全/隐私保护
探索Linux操作系统下的权限管理
【8月更文挑战第66天】在数字世界中,操作系统的权限管理就如同现实世界中的钥匙和锁,保护着我们的数据安全。本文将带你深入理解Linux系统中的权限设置,通过实际代码示例,让你掌握文件和目录权限的分配与管理技巧。准备好了吗?让我们开始这场关于权限管理的探险之旅吧!
46 14
|
9天前
|
人工智能 安全 数据安全/隐私保护
移动应用与系统:构建现代数字生态的双引擎在数字化浪潮席卷全球的今天,移动应用与系统作为连接用户与数字世界的重要桥梁,正发挥着越来越重要的作用。它们不仅改变了人们的生活方式,也推动了各行各业的创新与发展。本文将深入探讨移动应用开发的最新趋势、移动操作系统的演进历程以及两者如何共同塑造我们的未来。
随着智能手机的普及和移动互联网的快速发展,移动应用已成为人们日常生活中不可或缺的一部分。从社交媒体到移动支付,从在线教育到远程办公,移动应用无处不在,极大地丰富了我们的生活体验。与此同时,移动操作系统也在不断进化,为应用提供了更加稳定、高效的运行环境。本文旨在分析当前移动应用开发的热点技术、探讨移动操作系统的未来发展方向,并展望这两者如何相互促进,共同推动数字经济的繁荣。
|
8天前
|
安全 Linux 数据安全/隐私保护
探索Linux操作系统的文件权限管理
【9月更文挑战第29天】在数字世界中,文件权限管理如同保护我们隐私的锁。本文将带你了解如何在Linux系统中设置和管理文件权限,确保你的数据安全。我们将一起学习如何通过命令行工具来控制文件访问,就像学习一门新语言一样有趣。准备好了吗?让我们一起开启这场技术之旅!
|
11天前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
21 5
|
11天前
|
Java Linux Python
Linux环境下 代码java调用python出错
Linux环境下 代码java调用python出错
27 3
|
11天前
|
Oracle Java 关系型数据库
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
如果遇到"exec format error"问题,文章建议先检查Linux操作系统是32位还是64位,并确保安装了与系统匹配的JDK版本。如果系统是64位的,但出现了错误,可能是因为下载了错误的JDK版本。文章提供了一个链接,指向Oracle官网上的JDK 17 Linux版本下载页面,并附有截图说明。
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
|
13天前
|
Linux 编译器 开发工具
快速在linux上配置python3.x的环境以及可能报错的解决方案(python其它版本可同样方式安装)
这篇文章介绍了在Linux系统上配置Python 3.x环境的步骤,包括安装系统依赖、下载和解压Python源码、编译安装、修改环境变量,以及常见安装错误的解决方案。
21 1
|
13天前
|
Linux Python
linux之部署python环境&创建虚拟环境
linux之部署python环境&创建虚拟环境
|
13天前
|
Web App开发 Linux Python
linux上安装selenium环境及测试
该文章提供了在Linux CentOS上安装Selenium环境、Chrome浏览器及Chromedriver的详细步骤,并演示了如何以无头模式进行测试。
36 0