Linux编程概念

2022/4/30 7:12:41

本文主要是介绍Linux编程概念,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

  • 文件和文件系统


    • 文件是Linux系统中最基础最重要的抽象。Linux遵循一切接文的理念,所以大多数的交互操作都是通过读写文件来完成。文件必须先打开才能访问,文件打开方式有:只读,只写和读写三种方式
    • 文件打开后是通过文件描述符来引用的,所谓文件描述符是从打开文件关联的元数据到文件本身的映射,在Linux内核中,文件用一个int类型的整数来表示,称为文件描述符。
    • 文件描述符在用户空间共享,用户程序通过文件描述符直接访问文件。
    • 文件类型有七种,其中块设备文件类型、字符设备文件类型、命名管道、UNIC域套接字为特殊文件(以文件来表示的内核对象) 特殊文件的存在是为了使某些抽象可以适用于文件系统,贯彻一切皆文件的理念,Linux提供系统调用来创建特殊文件。
    • 文件系统:文件系统是用于有效的层次结构组织的文件和目录的集合。在文件和目录的全局namespace中,可以风别添加(挂载)和删除(卸载)文件系统。一般来说文件系统都是存在物理介质(磁盘)上的,但Liunx还支持只保存在内存上虚拟文件系统,以及存在于网络中的其他机器上的网络文件系统。物理文件系统保存在快设备文件中,如CD、硬盘、软盘、闪存。
    • 挂载点:每个文件系统都需要挂载到namespace的特定位置。挂载点可以访问文件系统的根目录。
    • 根文件系统:第一个被挂载的文件系统实在namespace的根目录“/”下。Linux必定有一个根文件系统,而其他文件系统的挂载点则是可以选的。
    • 文件类型包括(7种):
  1. 普通文件类[-]:
    Linux中最常见的一种文件类型,包括纯文本文件;二进制文件(ASCII);数据格式的文件(date)以及各种压缩文件。   在Linux中,可以从文件的任意字节开始读写。对文件的操作是从某一个字节开始的,即文件“地址”。该地址称之为文件位置或者文件偏移。 文件偏移默认为0,按字节读写文件偏移也随之增加,文件偏移也可以手动设置给定值,可以超出文件结尾,超出部分会默认填充0。不允许在文件起始位置写入,在文件中间写入会覆盖原有数据,并不会导致原有数据向后偏移。 文件大小通过字节数来计算,称为“文件长度”,文件长度可以通过截断操作进行改变,截断后大小可以大于原文件,超出部分填充0。 文件描述符,系统会为每个打开的文件实例提供唯一文件描述符,进程可以共享文件描述符,支持多进程使用同一个文件描述符。 文件虽然是通过文件名进行访的,但文件本身其实并没有直接和文件名进行关联,与文件本身关联的是索引节点inode(是文件系统为该文件分配的唯一整数值,但是在整个系统中不一定唯一),索引节点会保存和文件相关的元数据,如文件修改时间戳、所有者、类型、长度、文件数据的位置,但不包含文件名。
    索引节点、文件描述符

         inode 或i节点是指对文件的索引。如一个系统,所有文件是放在磁盘或flash上,就要编个目录来说明每个文件在什么地方,有什么属性,及大小等。就像书本的目 录一样,便于查找和管理。这目录是操作系统需要的,用来找文件或叫管理文件。许多操作系统都用到这个概念,如linux, 某些嵌入式文件系统等。当然,对某个系统来说,有许多i节点。所以对i节点本身也是要进行管理的。

        在linux中,内核通过inode来找到每个文件,但一个文件可以被许多用户同时打开或一个用户同时打开多次。这就有一个问题,如何管理文件的当前位移 量,因为可能每个用户打开文件后进行的操作都不一样,这样文件位移量也不同,当然还有其他的一些问题。所以linux又搞了一个文件描述符(file descriptor)这个东西,来分别为每一个用户服务。每个用户每次打开一个文件,就产生一个文件描述符,多次打开就产生多个文件描述符,一一对应, 不管是同一个用户,还是多个用户。该文件描述符就记录了当前打开的文件的偏移量等数据。所以一个i节点可以有0个或多个文件描述符。多个文件描述符可以对 应一个i节点。

     
  2. 目录文件类型[d]:
    就是目录,可使用# cd命令进入 通过文件节点访问文件很繁琐且潜在安全漏洞,因此访问文件通常是通过文件名从用户空间打开,而目录则提供访问文件需要的名称。 目录是可读名称到索引编号之间的映射,而名称和索引节点之间的配对称之为链接。 映射在物理磁盘上的形式是通过文件系统的内部代码来实现和管理的 目录和普通文件的区别:
    1. 从概念上可以把目录看做普通文件,其区别主要在于目录包含文件名称到索引节点的映射,内核通过该映射将文件名解析为索引节点。
    用户打开文件:
                 用户空间的应用请求打开指定文件  ---->  内核打开包含改文件的目录搜索该文件   ----->  内核根据文件名获取索引节点编号   ------>   通过索引节点编号找到该节点  
  3. 块设备文件类型[b]:
    存储数据以供系统存取的接口设备,简单来说就是硬盘。例:一号硬盘的代码/dev/hda1 块设备是作为字节数组来进行访问。设备驱动把字节映射到可寻址的设备上,用户空间可以按任意顺序随意访问数组中的任何字节。 块设备最小寻址单元:扇区(sector),扇区是设备的物理属性。扇区大小一般是2的指数倍,通常为512字节 文件系统最小寻址单元:块(block),块是文件系统的抽象,块大小一般是2的指数倍乘以扇区大小,通常比扇区大但必须小于页 内存最小寻址单元:页(page),页是内存的最小寻址单元  
  4. 字符设备文件类型[c]:
    串行端口的接口设备,例:键盘、鼠标等 字符设备是作为线性字节对列来访问。设备驱动程序把字节按顺序写入对列,用户空间程序按照写入对列的顺序读取数据。
  5. 套接字文件类型[s]:
    这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。最常在 /var/run目录中看到这种文件类型 socket是进程间通信的高级形式,支持不同进程间的通信,这两个进程可以在同一台机器上也可以在不同的机器上。
  6. 管道文件类型[p]:
    FIFOf(irst-in-first-out 先进先出)是用文件描述符来作为进程间通信的机制,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。进程间通信,创建无名管道或者有名管道时会用 普通管道是将一个程序的输出以“管道”的形式作为另外一个程序的输入 命名管道和普通管道一样,但它是通过FIFO特殊文件来访问的。  
  7. 链接文件类型[l]:
    链接文件是Linux文件系统的一个特性。如需要在系统上维护同一文件的两份或多份副本,除了保存多份单独的物理文件副本之外,还可以采用保存一份物理文件副本和多个虚拟副本的方法。链接是目录中指向文件真实位置的占位符。 在Linux中有两种不同类型的文件链接:
    1. 1.符号链接(软链接)
    2. 是一种特殊的文件,它指向 Linux 系统上的另一个文件或目录;这和 Windows 系统中的快捷方式有点类似,链接文件中记录的只是原始文件的路径,并不记录原始文件的内容
    3. 硬链接不能跨越多个文件系统因为索引节点百编号在自己的文件系统之外没有任何意义,为跨越文件系统建立链接,UNIX系统实现了符号链接。
      1. 多个文件系统
                   当计算机上有多于一个的文件系统时,Unix提供一种方法将这些属整合成一颗更大的树:也就是说用户看到的是一颗完好的目录树,但是实际上有两棵树,一个在磁盘1上,一个在磁盘2上。每个库都有一个根目录。
    4.              一个文件系统被命名为根文件系统。这棵树的顶端是整棵树的真正的根。另一个文件系统则被附加到根文件系统的某个子目录上。
    5. 符号链接类似于普通文件,每个符号链接有自己的索引节点和数据块,包含要链接的文件的绝对路径。所以符号链接可以指向任何地方,包括不同的文件系统上的文件与路径甚至不存在的文件和目录(指向不存在的文件的符号链接称之为坏链接)
    6. 符号链接缺点:比起硬链接符号链接会带来更多的开销(因为要存放要链接的文件的绝对路径),因为有效解析符号链接需要解析两个文件:一个是符号链接本身,二是该链接指向的文件。符号链接没有硬链接透明,使用硬链接只需要确定文件是否被多次链接,而符号链接需要特定的系统调用。   2.硬链接
    7. 硬链接是原始文件的一个镜像副本。创建硬链接后,如果把原始文件删除,链接文件也不会受到影响,因为此时原始文件和链接文件互为镜像副本。
    8. 从概念上看,Linux无法避免多个名字解析到同一个索引节点上。而事实上多个名子确实可以解析到同一个索引节点上,当不同的名称的多个链接映射到同一个索引节点时,我们称该链接为硬链接。
    9. 从目录中删除文件,需从目录结构中取消链接该文件,只需要从目录中删除该文件名和索引节点。而硬链接的存在,使得文件系统不能对每个取消链接操作时执行删除节点及其关联数据的操作。
    10. 确保删除所有链接之前不会删除文件,每个索引节点包含链接计数,记录该索引节点在文件系统中的链接数,增加链接时链接计数加一,取消链接时链接计数减一,只有当链接计数为0时,索引节点及其关联数据才会从文件系统中真正删除。
  •  进程


     

    • 进程在UNIX系统是仅次于文件的抽象概念
    • 进程是执行时的目标代码:活动的正在运行的程序。但是进程不只包含目标代码,他还包含数据、资源、状态和虚拟计算机。
    • Linux内存分区代码段(text):包含可执行代码和只读数据如常量   BSS:包含未初始化或初始化为0的的全局变量和静态变量   数据区(data):存放初始化全局变量和静态变量    栈(stack):存放局部变量   堆(heap):new/delete   malloc/free,手动申请释放
    • 进程与系统资源关联,系统资源是由内核决定和管理的。一般情况,进程只通过系统调用请求和管理资源(资源包括:计时器、挂起的信号量、打开的文件、网络连接、硬件和IPC机制)。
    • 进程资源以及该进程相关的数据和统计保存在内核中该进程的进程描述符中。
    • 进程是一种虚拟抽象。进程内核同时支持抢占式多任务和虚拟内存,为每个进程提供虚拟处理器和虚拟内存视图。
    • Linux进程状态:运行,中断,不可中断,僵尸,停止状态
    • 从进程角度看,系统好像完全由进程控制,实际上系统会给每个进程分配一个很短的时间,当一个进程运行完后再次运行时系统内核会无缝透明地抢占和重新调度进程,事实上所有进程共享系统处理器。
      • Linux如何识别进程

        由于 Linux 是一个多用户系统,意味着不同的用户可以在系统上运行各种各样的程序,内核必须唯一标识程序运行的每个实例。

        程序由它的进程 ID(PID)和它父进程的进程 ID(PPID)识别,因此进程可以被分类为:

        • 父进程 - 这些是在运行时创建其它进程的进程。
        • 子进程 - 这些是在运行时由其它进程创建的进程。
      • 进程层次结构
        每个进程都有唯一的正整数标识,称之进程ID(pid),第一个进程init进程的pid是1,后面每个进程都有一个新的唯一的pid。 在Linux中,进程有严格的层次结构,即进程树。         进程树的根是第一个进程(init进程),而新的进程是在init进程的基础上通过系统调用fork()创建的。         fork()会创建调用进程的副本。原进程称之为父进程,fork()创建的进程称之为子进程。         除了init进程,每个进程都有父进程,如果父进程先于子进程终止,内核会将Init进程指定为它的父进程。 等待终止进程:当进程终止时,并不会立即从系统中删除。相反的,内核将在内存中保存该进程的部分内容,允许父进程查询其状态。 一旦父进程确定某个子进程已经终止,该子进程就会被完全删除。
      • init进程
        init 进程是系统中所有进程的父进程,它是启动 Linux 系统后第一个运行的程序;它管理着系统上的所有其它进程。它由内核自身启动,因此理论上说它没有父进程。 init 进程的进程 ID 总是为 1。它是所有孤儿进程的收养父母。(它会收养所有孤儿进程)。
      • 守护进程
        这是后台进程的特殊类型,它们在系统启动时启动,并作为服务一直运行;它们不会死亡。它们自发地作为系统任务启动(作为服务运行)。但是,它们能被用户通过 init 进程控制。
      • 僵尸进程

        每个进程在结束后都会处于僵死状态,等待父进程将其释放资源,处于该状态的进程已经结束,但父进程还没有释放其系统资源。

        由于某种原因,父进程在子进程退出前退出,则所有子进程就变成一个孤儿进程,拖没有相应处理机制,则孤儿进程会一直处于僵死状态,资源无法释放。这种僵死的孤儿进程即僵尸进程。

        此时解决方法是在启动进程内找一个进程作为这些孤儿进程的父进程,或者直接让init进程作为它们的父进程,进而释放孤儿进程占用的资源。

  • 线程


    • 每个进程包含一个或多个线程,线程是进程内的活动单元。或者说:线程是负责执行代码和管理进程运行状态的抽象。
    • 绝大多数进程只包含一个线程称之为单线程,包含多个线程的进程称之为多线程。
    • 线程包括:栈、处理器状态、目标代码的当前位置(通常是保存在处理器的指令指针中)。除此之外,进程的其他部分由所有线程共享,最主要的是进程地址空间。所以线程在维护虚拟进程抽象时也共享虚拟内存抽象。
    • 在Linux系统内部,Linux内核实现了独特的线程模型:它们其实是共享某些资源的普通进程。
  • 用户和组


    • Linux通过用户和组进行权限认证,每个用户和一个唯一的正整数关联,该整数称之为用户ID(uid)。
    • 每个进程和一个uid关联,用来识别运行这个进程的用户,称之为进程的真实uid.
    • 在Linux内核中,uid是用户的唯一标识。但是,用户一般通过用户名而不是id表示。
    • 用户名及其对应的uid保存在/etc/passwd中,而系统库会把用户名映射到对应的uid上。
    • 登录流程
      • 用户向login程序提供用户名和密码
      • 如果正确,login程序会根据/etc/passwd为用户生成login shell
      • 把用户id作为该shell进程的uid
      • 子进程继承父进程的uid
    • 超级用户root的uid是0。几乎可以执行所有的操作。例:只有root用户可以修改进程的uid,因此,login进程是以root身份运行的。
    • 进程拥有四个id,真实的uid,有效的uid,保留uid,文件系统uid。
    • 真实uid总是启动进程的用户uid;有效的uid在不同情况下会发生改变,从而支持进程切换成其他用户权限来执行;保留uid保留原来的有效uid,其值决定了用户将切换成哪个有效uid;文件系统uid通常和有效uid等效,用于检测文件系统的访问权限。
    • 每个用户属于一个或多个组,因此每个进程和相应的组ID(gid)关联,也包括上述四个ID。
  • 权限


    • Linux的标准文件权限和安全机制与UNIX的一致。
    • 每个文件都有文件所有者、所属组、三个权限位集合
    • 三个权限位每个权限位描述了所有者、所属组、其他人对于文件的读(r),写(w),执行(x)的权限。
  • 信号


    • 信号是一种单向异步通讯机制。
    • 信号可能是从内核发送到进程,也有可能是从进程发送到进程,也有可能是进程发送给自己。
    • Linux内核实现了约30种信号,每一个信号都是由一个数值常量和文本名表示。
    • 信号会干扰正在执行的进程,不管当前进程正在做什么都会立即执行预定义的操作。
    • 除了SIGKILL(进程中断)和SIGSTOP(进程停止),当进程接收到信号时,可以控制正在执行的操作。
    • 进程可以接收默认的信号处理操作,此外进程还可以选择显示忽略或处理信号。
    • 忽略的信号会被丢弃,不做处理。处理信号会执行用户提供的信号处理函数,程序接收到信号时会立即跳到处理函数执行,执行完毕后再继续执行未执行玩的程序。
  • 进程间通信


    • Linux内核实现了大多数UNIX进程间通信(IPC)机制——包括Systen V 和 POSIX 共同定义和标准化的机制——实现自定义的机制
    • 进程间通讯方式
      • 管道(无名管道、有名管道)
      • 信号
      • 信号量
      • 消息对列
      • 共享内存
      • 快速用户空间互斥(futex)
  • 头文件


    • Linux系统编程离不开大量的头文件。
    • 内核本身和glibc都提供了用于系统编程的头文件,这些头文件包括标准C库(如<string.h>) 以及一些UNIX的贡献(如<unistd.h>).
  • 错误处理


    • 在系统编程中,错误是通过函数的返回值和特殊变量errno描述。gblic为库函数和系统调用1提供透明errno支持。
    • 函数通过特殊返回值(一般-1)通知调用函数发生错误,而errno则用来定位错误原因。
    • 变量errno在<errno>中定义如下
      • extern int errno;
    • errno的值只有当errno设置函数显示错误后(通常返回-1)才生效,且在程序后续执行过程中都可以修改其值。
    • errno变量可以直接读写,它是可修改的左值。errno的值和特定错误的文本描述一 一 对应。
    • C库提供了很多函数,可以把errno值转换成对应的文本,只有错误报告以及类似操作时才需要。
      • perror()
        • #include <stdio.h> void perror(const char *str); 该函数向stderr(标准错误输出)打印以str指向的字符串为前缀,紧跟一个冒号,然后是由errno表示的当前错误的字符串。
      • strerror()
        • #include<string,h> char * strerror(int errnum); 该函数返回由errnum描述的错误的字符串指针。字符串可能不会被应用程序修改,但是后续会被perror() 和 strerror() 函数调用修改,所以此函数不是线程安全的。
      • strerror_r()
        • #include<string.h> int strerror_r(int errnum, char *buf, size_t len); 相较函数strerror(),strerror_r()函数是线程安全的,它向buf指向的长度为len的缓冲区中写入数据。成功返回1,失败返回-1。该函数错误时也设置errno。
    • 对于某些函数,在返回值类型范围内返回的值都是合法的。在这种情况下,在调用前error必须设置为0,且调用后还会检查(这些函数保证在真正错误时返回非0的errno值)。
      • 例: errno = 0; arg = strtoul(buf , NULL , 0); if(errno)
        • perror("strtoul");
    • 在单线程中errno是个全局变量。然而多线程中,每个线程都有自己的errno,所以errno是线程安全的。


这篇关于Linux编程概念的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程