linux系统怎么学(linux操作系统怎么学)

linux系统怎么学(linux操作系统怎么学)计算机启动后 CPU 从默认的地址处读取 NOR Flash 存储器中的固件执行 固件检测计算机各种设备工作正常后 去辅存查询下一个程序执行 这个程序就是操作系统启动入口 或者操作系统安装程序执行入口 查询方式有如下两种 此方式在辅存的第一个扇区或第一个页开始查询 查询长度为 512 字节 若此段数据的末尾为 0x55 和 0xaa 则认为这段数据是操作系统启动入口程序 也称为操作系统引导程序

大家好,我是讯享网,很高兴认识大家。



使用Legacy方式的计算机中安装多个操作系统时,需要在第一个引导程序之后再分出多个引导,分别引导不同的操作系统启动,主引导程序损坏后其它分支引导程序都将无法执行,所有的操作系统都将无法启动。

使用UEFI方式的计算机安装多个操作系统时,每个引导程序互不干扰,任何一个引导程序损坏都不影响其它操作系统启动。

与Linux内核组成操作系统的基础GNU组件如下:

1.GCC,高级语言代码编译器,主要用于编译C、C++代码。

2.Binutils,程序开发工具集,包括汇编器、连接器、其它相关工具。

3.Glibc,程序运行库,使用C语言制作的一组目标文件、动态连接库文件,实现了程序运行所用的常用功能,其中很多底层功能通过系统调用实现,很多用户程序、函数库、类库都会通过Glibc使用系统调用,而非通过内嵌汇编代码使用系统调用。

4.GTK,用于制作程序图形界面,同时非GNU项目的QT也广泛流行。

5.Grub,多操作系统启动管理工具。

系统调用的执行方式与C语言函数类似,执行时也需要输入参数,每个系统调用都有一个编号,比如读取文件的系统调用编号为0、写入文件的系统调用编号为1,用户程序使用此编号指定需要使用的系统调用。

用户程序使用的指令权限低于操作系统内核,无法使用跳转指令直接执行系统调用,用户程序使用系统调用是通过中断实现的,首先用户将系统调用编号以及相关参数写入寄存器,之后发出一个编号为0x80的内中断,系统内核接收到中断后,根据寄存器中存储的系统调用编号和参数确定执行哪个系统调用,以及如何执行,可以认为系统调用就是操作系统内核提供的一组中断处理程序。

使用系统调用的具体步骤如下:

1.将系统调用的编号写入rax寄存器。

2.将系统调用的参数写入函数传参寄存器,比如写入文件系统调用有三个参数,使用rdi、rsi、rdx寄存器存储。

3.使用 int 0x80(x86处理器) 或 syscall(x86-64处理器) 指令发出内中断。

4.系统内核接收到内中断后,根据寄存器中的数据执行用户程序需要使用的功能。

5.系统调用执行完毕后,执行恢复现场指令,之后返回用户程序执行。

下面介绍一个简单的汇编程序代码,功能是在终端输出“阿狸”。

执行程序时操作系统会为程序文件分配其占用的内存,另外再分配一个物理地址连续的空间供程序存储频繁使用的函数内局部数据,为了更快的读写数据这段内存经常使用栈方式进行管理,所以也称为栈空间。

虚拟地址与物理地址都使用IP寄存器指定,一个进程可用的虚拟地址空间容量与计算机可用的物理内存空间容量相同,理论上一个进程可以使用整个物理内存。

程序指令使用的虚拟地址从0x开始分配,而非从0开始,0x是程序文件在内存中的第一个文件内部地址,绑定程序文件的第一个数据,0x之前的虚拟地址被程序废弃不用,之所以这样规定完全是历史原因,没有特殊优势,就像中国高铁的轨距为什么是1435毫米一样。

内存碎片主要是因为进程向操作系统申请内存产生的,内存碎片分为两种:页内碎片、页间碎片,linux主要管理页间碎片,将暂时不使用的页间碎片整理到一起,移动到地址连续的空闲内存区域,从而清理碎片,页间碎片过多会导致无法分配类似栈空间这种需要地址连续的内存。

虚拟内存与虚拟地址是不同的概念,不要搞混,虚拟内存是使用外存模拟出来的内存,而虚拟地址是进程在内存中执行时使用的内部地址,这两个概念不同的人有不同的理解方式,也就有不同的名词解释方式,计算机知识中很多名词都没有统一解释,新手很容易越听越迷糊。

1.init进程,用于启动用户进程。

2.kthreadd进程,用于启动系统内核进程。

早期的进程没有线程,进程内部代码只能单线执行,进程的多个子功能需要依次排队执行,之后为了让进程中的功能同时执行发明了线程,进程内部每一组需要独立执行的代码制作为一个线程,每个线程都有独立执行能力、可以与其它线程同时执行。

但是注意,线程并不是把进程内所有数据全部分组,而是只将需要独立执行和独立使用的全局函数、全局数据分组,未分组的数据直属进程,所有线程都能使用,程序以线程为单位执行,操作系统执行的是线程而非进程,每个线程都有自己的栈空间,直属进程的函数不能自己执行,需要被其它线程调用才能执行,此时其使用调用者的栈空间。

main函数是程序的主线程,若没有创建其它线程,则程序就只有这一个线程。

2.运行状态,进程正在执行,内核将此状态与准备就绪状态记录为同一种状态,内核并不会区分准备就绪与运行状态。

3.轻度睡眠状态,进程暂停执行,可以被信号恢复执行。

4.中度睡眠状态,进程暂停执行,只能被重要的信号恢复执行。

5.深度睡眠状态,进程暂停执行,不能被信号恢复或者终止执行,不对信号做任何反应,只能由操作系统重新调用它执行。

6.跟踪状态,进程由调试器设置为暂停状态,可由调试器恢复执行。

7.挂起状态,也称停止状态,进程暂停执行,并且短期内不会恢复执行,操作系统将进程数据从内存转移到外存,进程需要执行时再读取到内存。进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU信号后会进入挂起状态,向挂起状态的进程发送SIGCONT信号会恢复执行。

8.僵尸状态,进程终止执行,进程数据在内存中删除,但是保留进程描述符,其中存储了进程基础属性信息、进程终止相关信息,用于供父进程查询使用,若父进程设置了子进程退出事件,则此时父进程会收到子进程终止的信号通知。

9.死亡状态,进程描述符从内存删除,内存中不再有此进程的任何信息。

其中暂停状态比较复杂,此状态又会细分为多种,不同的暂停状态恢复方式也不同。

系统用户执行的进程称为用户进程,使用最低级别指令权限。

当用户进程执行系统调用时,此时进程属于内核状态,简称内核态,当用户进程执行自己的代码时,此时进程属于用户状态,简称用户态。

同一个系统调用可以被多个进程同时使用,系统调用每次执行时都会分配一个独立的内核栈,所以系统调用同时执行多次时不会产生冲突。

其中打开文件列表用于存储进程打开的所有文件的属性,进程每打开一个文件就会使用一个结构体存储此文件属性,这个结构体称为文件描述符,进程所有的文件描述符放在一个数组中,此数组称为打开文件列表,进程可以打开的文件数量上限等于打开文件列表元素数量的上限。

所有的用户进程最终都会关联到init进程,init进程的编号为1,它是linux启动后执行的第一个用户进程。

同源子进程有两种创建方式,主要区别就是全局变量是否与父进程共用:

1.使用fork函数创建同源子进程,全局数据不共用,子进程会复制一份父进程的全局数据供自己使用,子进程修改全局数据不影响父进程。

2.使用clone函数创建同源子进程,可以设置哪些全局数据为共用,任何一个进程修改了共用的全局数据都会影响其它进程,这种同源子进程更类似线程功能。

最早的同源子进程会复制父进程中所有的数据,但是同源子进程使用的函数与父进程完全相同,只是全局变量可能会不同,这份相同的指令数据完全没必要复制一份,父进程与子进程共同使用即可,这样既节省内存、又节省了复制数据占用的CPU执行时间,因此操作系统对同源子进程的创建步骤进行了简化,创建同源子进程时只是创建一个进程描述符、并分配一个栈空间,不复制指令数据,两个进程使用同一份指令数据,而全局变量按需复制,当同源子进程或父进程任何一方需要修改全局变量时,两个进程将不再共用此全局数据,系统内核会将需要修改的全局变量复制一份给同源子进程,供其单独使用,这种管理方式称为写时复制,有了写时复制技术以后,同源子进程也称为轻量级进程。

模拟线程

Linux没有线程,而是通过同源子进程模拟线程,进程内的功能需要同时执行时就创建他的同源子进程分别执行不同的函数,这样做看似不合理,但是因为有写时复制技术也不会降低执行效率。

因为直接将同源子进程当做线程使用不方便,所以Linux提供了使用方式更简单的线程API:pthread,pthread使用clone函数创建同源子进程模拟线程,实现与父进程共享全局数据。

若进程创建多个线程,main函数为主线程,其它函数为副线程,主线程会分配容量更大的栈空间,也就是同源子进程会分配容量更小的栈空间。

若进程不创建线程,则进程默认就只有一个主线程,也就是此进程没有同源子进程。

用户进程中,非init进程创建的异源子进程会自动变成init的子进程,由init负责管理,不再属于创建者的子进程,而内核进程创建的异源子进程依然属于创建者的子进程,进程关系不会发生改变。

创建异源子进程最直接的方式就是操作系统分配一段内存空间,然后将程序数据写入到此处,之后执行程序,但linux却不是这样做的,linux会首先删除父进程在内存中的数据,之后将新程序的数据写入内存,从而创建一个新进程,为了防止父进程被删除,创建异源子进程时需要首先创建父进程的同源子进程,之后使用同源子进程创建异源子进程,至于为什么使用这种创建方式,完全是因为历史原因,而不是故意设计成这样,虽然看似这种创建方式不太合理,但是因为有写时复制技术也不会降低太多性能。

总结,子进程分为两类,同源子进程和异源子进程,这两种进程的名称只在本文中,其它文章将同源子进程称为轻量级进程、将异源子进程称为子进程。

队列中的进程并非执行完毕后再切换到下一个进程,操作系统将CPU的执行时间分为多个片段,操作系统使用时间片为进程分配执行时间,时间片到期后进程暂停,切换到下一个进程执行,从而实现多个进程同时执行,时间片的长度并不是固定的一种,而是有多种,对于CPU消耗型的进程会分配长度更大的时间片,对于IO设备消耗型的进程会分配更短的时间片,同一类进程会再分为多种优先级,优先级高的进程会分配更多的时间片,进程切换执行时,操作系统会将寄存器的值保存在内存中,进程恢复执行时首先还原寄存器的值,之后执行进程。

操作系统不同于人的大脑,他无法完全避免进程之间的死锁,进程在执行期间产生的死锁无法处理,若操作系统增加管理进程死锁的功能则工作会大量增加,占用大量的CPU执行时间,从而导致程序执行速度降低,进程死锁的问题只能靠进程自己解决,比如进程在等待资源时设置时间上限,若超时则释放自己占用的资源,让其它进程使用。

1.主动退出,进程内代码执行完毕,之后自行使用系统调用告知操作系统要求退出,操作系统接收到退出信息后,删除它占用的内存,不再调度此进程。

2.被动退出,进程自身没有执行完毕,但是被其它程序发送信号要求其退出,或者操作系统因为某些原因强制其退出。

无论进程使用哪种方式退出,默认情况下操作系统都会向其父进程发送一个信号,告知有子进程退出,之后父进程可以通过进程描述符查询是哪个子进程退出、以及退出原因,并清理僵尸子进程的描述符,父进程也可以设置不接收子进程退出事件,此时子进程结束运行后直接删除其在内存中的所有数据,包括进程终止后的描述符,直接进入死亡状态。

在辅存中读写数据是以扇区或页为单位进行的,一个扇区或一个页应该分配到同一个分区中,否则会影响数据读写速度。

为辅存创建多个分区有如下两个优势:

1.不同分区存储不同文件,方便管理,互不影响。

2.不同分区创建不同文件系统,不同文件系统有不同的优势。

分区表按类型分为两种:MBR、GPT。

64字节的分区表再分为四组,每组16字节,每组存储一个分区的属性,包括分区起始处、结束处、总容量、分区类型以及文件系统类型、是否为活动分区(存储操作系统引导程序的分区),使用MBR分区表的辅存最多创建4个分区,并且辅存最大容量限制为2TB。

1.可以创建更多数量的分区。

2.管理容量超过2TB的存储器。

3.使用更大容量的操作系统引导程序。

分区表类型与操作系统引导程序类型绑定,使用Legacy引导方式时,引导程序只能放在MBR分区表中,使用UEFI引导方式时,引导程序只能放在使用GPT分区表的FAT32(一种文件系统)分区中。

文件系统为每个文件分配一个唯一的数字编号,通过文件编号调用此文件,因为编号不方便人记忆,所以每个文件还会绑定至少一个文件名称,文件名与文件编号绑定,从而可以使用文件名调用文件。

文件系统中的文件可以继续分类管理,方便查询不同的文件,每一类文件的信息使用另一个专用文件存储,此文件称为目录文件,目录文件存储了归属此目录的所有文件的名称和其它属性,分类可以嵌套使用,也就是可以在目录中创建子目录,操作一个文件时需要同时指定文件所属目录的名称以及文件的名称,这个完整的名称称为文件路径,文件系统会首先读取路径中的目录文件,并在其内部查询指定文件,之后操作文件。

ext4文件系统的i节点原型如下:

成员i_mode记录文件的类型与基础访问权限。

文件系统将文件类型分为以下几种:普通文件、目录文件、设备文件(字符设备文件、块设备文件)、通信文件(FIFO文件),其中设备文件与通信文件不占用数据块,设备文件绑定IO设备编号,读写设备文件会转换为读写IO设备端口,通信文件绑定内存地址,读写通信文件会转换为读写内存单元。

文件权限用于记录哪些用户拥有读、写、执行权限,文件权限之后会详细介绍。

成员i_block是一个数组,用于存储文件占用的数据块编号,长度15,每个元素作用如下:

1.元素1-12,直接记录数据块的编号,若文件占用的数据块不超过12个,则使用前12个元素即可完全记录其占用的数据块,这12个元素可以看做是一级指针,直接指向数据块。

2.元素13,类似二级指针,指向一个数据块,此数据块中的数据存储文件占用的数据块编号。

3.元素14,类似三级指针,同上。

4.元素15,类似四级指针,同上。

当文件需要用到i_block的第15个元素时,文件将会占用( 12 + 二级指针容量 + 二级指针容量*三级指针容量 + 二级指针容量*三级指针容量*四级指针容量 )个数据块,超过此容量的文件不能存储,但是也很难有如此大的文件。

目录元素结构体有两种原型,分别如下:

使用文件路径调用文件时,文件系统会首先寻找指定的目录文件,之后在目录文件中通过文件名查询到指定文件,从而找到此文件的i节点,之后通过i节点调用文件,若文件直接存放在分区中,则此文件的属性使用一个特殊的目录文件记录,这个目录称为根目录,根目录用于记录直接在分区中存储文件的属性,文件系统使用文件路径查询文件时从根目录开始查询。

连接,表示将多个物体组合为一个整体的行为,链,表示连接使用的绳索,在计算机知识中经常有人将链称为链接,同时又有人将连接也称为链接,链接一词源于对英文link的翻译,link本意是指用于连接两个文件或两组数据所有的特殊数据,此文中链接表示名词,表示连接所用的数据,实际上链接简称为链更合适,避免与连接混用导致误解,另外其它文章中的链接库在本文中称为连接库。

在文件系统中,文件路径也称为文件硬链接,硬链接与文件i节点绑定,一个文件只能有一个i节点,但是可以有多个硬链接,也就是可以为一个文件绑定多个名称、或者多个目录,此时使用任何一个硬链接都会调用同一个文件,单独删除任何一个硬链接都不影响文件,只有将文件的所有硬链接都删除文件才会在文件系统中删除。

对应的还有文件软链接,文件软链接是一个独立的文件,只不过此文件使用字符串存储了另一个文件的路径,读写软链接文件时会转换为读写其指向的文件,若文件软链接指向的文件被删除,则使用软链接访问指向的文件时会产生错误。

使用硬链接和软链接都可以调用其指向的文件,区别在于文件硬链接是通过文件系统实现的,链存储在目录中,文件软链接是通过操作系统实现的,链存储在另一个普通文件中,文件软链接建立在文件硬链接功能之上。

文件在文件系统中删除时只是将文件i节点编号、文件所属目录条目编号记录为废弃状态,新文件可以重新使用这些废弃位置存储文件属性,并不会将文件属性、文件内容占用的存储单元全部设置为0,基于以上原理,在文件系统中删除的文件也是可以恢复的,只需被删除文件的i节点、以及文件内容占用的存储单元没有被其他文件再次占用、保留了原来的状态。

如果需要将文件彻底删除,可以将文件占用存储单元的状态全部打乱,全部写入0或1,文件粉碎机就是此原理,但是注意一点,粉碎的只是文件内容,不包含存储在目录文件中的文件名称,文件名称还是可能被恢复的,若需隐藏粉碎文件的名称,可以在粉碎文件之前修改文件名为随机字符。

其它分区需要与根分区中的目录进行直接或间接的绑定才能使用,这个绑定所用目录称为挂载点,访问挂载点就等于访问其绑定的分区,挂载点不仅可以绑定分区,也可以绑定分区中的一个目录。

使用挂载点访问分区有如下两个优势:

1.不限制FHS规定的分类只能绑定根分区目录,比如FHS规定根分区中的boot目录存储操作系统引导文件,若需要将这些文件单独存储在一个分区中,就可以将boot目录绑定另一个分区,而不是直接使用根分区中的boot目录存储文件。

2.扩展分区容量,在此分区中创建一个目录作为挂载点绑定另一个分区,从而增加此分区的容量。

安装Linux系统时需要设置使用哪个分区作为根分区,根分区使用 / 符号访问,根分区内的文件和目录使用 “/文件名” 的方式访问,比如 /boot,表示访问根分区中的boot目录。

FHS规定的根分区常用目录如下:

/boot,存放系统引导文件。

/etc,存放系统配置文件。

/run,绑定一个内存目录,Linux启动后会将各种系统相关信息使用内存文件存储,程序可以读取这些文件进行查询,因为绑定的是内存,所以每次操作系统启动后此目录都会清空。

/srv,存放网络通信功能相关文件,比如HTTP、FTP、SMTP网络通信所用文件,个人用户通常会安装第三方浏览器、电子邮箱、和其它各种网络通信软件,这些软件会在自己的安装目录中存储数据,srv一般只在服务器中使用。

/dev,存放设备文件。

/usr,存放操作系统提供给用户使用的一些文件,内部会定义很多子目录,文件会继续细分存储在usr的子目录中。

/usr/lib,存放连接库文件。

/usr/bin,存放可执行文件。

/usr/sbin,存放重要的可执行文件,一般只有root用户有权限执行。

/usr/include,存放各种C语言函数库文件。

/usr/share,存放文本文件,比如各种帮助文档。

/usr/local,存放用户程序,用户自己安装的软件可以放在这里。

/var,存储操作系统运行、用户程序运行所需的经常变化的文件,比如系统日志、数据库、缓存文件,与usr类似,也是在内部创建各种子目录继续分类存储文件。

/home,存放普通用户主目录,每个用户都有一个专用的主目录,放在/home内,比如用户ali的主目录为/home/ali。

/root,root用户主目录。

/opt,存放用户自己安装的程序,与 /usr/local 相同。

/tmp,存放临时文件,程序执行期间产生的临时文件放在这里,程序执行完毕既废弃,此目录内的文件应该定期清理,防止废弃文件占用过多存储空间。

/recovery,回收站目录,回收站内文件会在超过规定时间后自动删除。

/proc,绑定一个内存目录,内部文件存储进程信息、系统内核信息、硬件设备信息。

/sys,绑定一个内存目录,内部文件存储系统内核信息、硬件信息,与proc类似。

/media,用于绑定外接媒体类存储器,比如光盘、软盘,需要在内部创建子目录作为挂载点,而不是直接绑定media。

/mnt,用于绑定临时添加的辅存,需要创建子目录作为挂载点。

/lost+found,文件系统发生错误后,将丢失的一些文件碎片存放在这里。

操作文件时需要指定文件路径,文件路径以 / 符号开头,/ 符号表示根分区,之后编写目录名、文件名,目录名与文件名之间也需要编写 / 符号,示例:/home/ali/a.txt。

若需要指定一个目录,则目录末尾添加 / 符号,示例:/home/ali/data/,data表示一个目录,某些情况下目录末尾的 / 符号也可以省略。

进程工作目录

当一个进程需要频繁操作一个目录内的文件时,每次都指定文件的完整路径很繁琐,为此linux为进程提供了工作目录的功能,工作目录表示本进程频繁使用的目录,指定工作目录内的文件时无需编写完整路径,只编写工作目录之后的路径即可,操作系统会自动将简写路径与工作目录进行组合,形成为完整路径。

比如一个进程设置/home/ali/为工作目录,若有一个文件的完整路径为/home/ali/data/a.txt,则在此进程中使用data/a.txt即可指定此文件,简写路径无需使用/符号开头。

进程执行时默认继承父进程的工作目录,之后可以重新设置自己的工作目录。

环境变量

环境变量用于设置操作系统的某些属性值,比如LANG变量设置操作系统使用的语言类型,另外一个经常使用的环境变量是PATH,PATH记录用户在终端输入命令时常用的目录,使用PATH变量记录的目录中的文件时可以使用简写路径,shell会在PATH记录的目录中查询使用简写形式指定的文件或子目录。


讯享网

PATH变量本身是shell脚本文件中定义的一个字符串变量,PATH可以存储多个路径,路径之间使用:符号隔开,示例:PATH=“/home/ali:/opt”;,这里存储了两个目录。

存储PATH变量的shell脚本文件如下:

1https://www.bilibili.com/read/etc/profile,此文件存储的环境变量对所有用户有效。

2https://www.bilibili.com/read/home/用户名/.bashrc,此文件存储的环境变量对指定用户生效。

进程使用环境变量时并非临时读取,而是在进程执行时就将其读取到内存中备用,所有的环境变量使用一个数组存储,这个数组称为环境列表,子进程执行时会复制一份父进程的环境列表,之后子进程可以按需修改自己的环境列表。

相对路径使用方式

有了工作目录和环境变量后,指定文件路径时就可以使用简写方式,这种简写的文件路径称为相对路径,完整路径称为绝对路径。

程序代码使用的相对路径与进程自己的工作目录组合为完整路径,示例:fopen(“a.txt”, “a”);。

预处理指令#include在<>符号内使用的相对路径,在环境变量记录的目录中查询指定文件,并组合为完整路径,示例:#include <stdio.h>。

预处理指令#include在“”符号内使用的相对路径,与编译器工作目录组合为完整路径,示例:#include “ali.c”。

2.使用..符号表示工作目录的上一层目录,比如工作目录为 /home/ali/,则..表示 /home 目录。

3.使用~符号表示本用户的主目录,比如本用户主目录为 /home/ali/,则~表示 /home/ali 目录。

注:程序进行读目录操作时,总会读取到.和..两个虚拟子目录,但是这两个子目录是不存在的,它们存在的作用只是方便快速访问,因为是虚拟的所以也不能删除,读目录时直接忽略.和..即可。

1.ELF文件,各种类型的程序文件统称。

2.软链接文件,存储另一个文件的路径。

3.设备文件,绑定IO设备。

4.通信文件,存储通信数据。

5.用户文件,用户自建的各种类型文件统称为用户文件,比如:文本文件、图片文件、音频文件、视频文件。

各种类型文件的读写行为结果不同。

1.读取设备文件、通信文件时,若没有数据可读则进入暂停等待状态,直到有数据后恢复执行,此类文件也可以设置为不进入等待状态、直接返回。

2.读取其它文件时,若没有数据可读则直接返回。

设备文件存储在/dev目录中,常用设备文件命名方式如下:

1.sd,绑定SATA、USB接口连接的辅存设备,名称添加字母区分不同的辅存,比如sda、sdb、sdc,之后再添加数字区分不同的分区,比如sda1、sda2,分区编号不一定是连续的,可以在进行分区时自定义分区编号。

2.nvme,绑定使用NVMe协议的M.2接口连接的辅存设备。

3.cdrom,光盘驱动器。

4.tty,终端,添加数字区分不同的终端,比如tty1、tty2。

5.null,空设备文件,不会绑定任何硬件设备,向此文件写入的数据会被丢弃,比如执行一个命令时不需要使用它在终端输出的内容,可以将输出内容重定位到此文件。

通信文件是在内存中创建的文件,通信文件主要有两种:FIFO文件(存储本机进程通信数据,也称管道文件)、Socket文件(存储网络通信数据,也称套接字文件),FIFO文件还会在文件系统绑定一个路径,可以通过此路径打开内存中的FIFO文件。

Linux将进程与IO设备的通信数据、进程之间的通信数据、网络通信数据都使用文件进行存储,这样做的优势是统一通信方式,打开相关文件读写数据即可实现各种类型的通信,初学者可能意识不到这种优势,在学习Linux的过程中会慢慢了解这种优势的重要。

很多人将这种方式称为一切皆文件,但这种解释并不合理,甚至很容易让人产生误解,准确的解释是Linux将一切通信数据使用文件存储。

每个进程都会自动打开三个通信文件:标准输入文件、标准输出文件、标准错误文件,这三个文件是操作系统自动为进程创建的,用于进程与终端通信,具体作用如下:

1.标准输入文件(stdout),文件描述符在打开文件列表的下标为0,存储终端输入数据,进程读取的数据会在文件中删除,未读取的数据依然保存在标准输入文件内。

2.标准输出文件(stdin),文件描述符下标为1,存储终端输出数据,程序以增加数据的方式写入数据,不会覆盖原有数据,终端读取其中的数据进行显示,读取后的数据会被删除。

3.标准错误文件(stderr),文件描述符下标为2,存储程序执行错误后操作系统在其所用终端输出的数据,用于告知用户发生了什么错误,进程也可以向此文件写入数据,若进程被禁止使用终端输出功能,可以通过向标准错误文件写入数据绕过限制。

标准输出文件中的数据被终端读取后会被删除,但是这些数据占用的内存单元保持了原有状态,若这些内存单元没有被写入新数据,则某些恶意进程可以读取这段内存单元查询本进程之前输出的数据。

注:在linux中也可以向标准输入文件写入数据,写入的数据会在终端显示,等同于转换为向标准输出文件写入,但是这种代码是不合规的,应该避免使用这种代码。

在Linux系统中,系统内核操作文件的类型由文件头决定,文件内部的起始位置会存储一些说明文件类型、文件属性的数据,这些数据称为文件头,使用文件头确定文件类型的方式对程序来说很方便,但是对计算机用户却不方便,用户无法通过文件名快速确认文件类型,为此很多人也会参考Windows系统的方式,使用后缀名说明文件的类型,比如后缀out表示可执行文件、后缀o表示可重定位文件、后缀so表示动态连接库文件,但是这些后缀只用于计算机用户自行使用,Linux会忽略它,Linux依然使用文件头确定文件类型,比如你将一个可执行文件命名为a.so依然可以正常执行,而在Windows中这种行为会出错、无法执行。

在Linux系统中,用户程序操作文件的类型使用后缀名确定,也有少部分使用文件头确定,文件后缀名有跨平台的通用规则,比如文本文件后缀txt,不同的操作系统、不同的文件查看器都使用此规则确定用户文件类型,若将一个音频文件命名为a.txt则不会被音频播放器识别为音频文件,但是可以被文本文件编辑器打开,此时会将音频数据当做字符显示。有些功能复杂的用户文件会有文件头,用于说明文件属性,功能简单的用户文件没有文件头,比如文本文件,它直接从文件起始处存储字符编码。

文件权限由文件系统记录,如果将文件复制到另一种类型的文件系统分区中,文件权限可能会丢失,不同文件系统对文件权限的记录方式不同,彼此不一定兼容。

比如将文件从Linux使用的EXT4文件系统复制到Windows使用的NTFS文件系统中,记录的文件权限会消失。

文件系统记录文件权限时,会将用户分为如下三类:文件所属用户(文件属主)、文件所属用户所在用户组的其它用户(文件属组)、其它用户,文件系统可以为这三类用户分别设置不同的权限,但是文件基础权限对root用户不生效。

另外Linux还支持使用ACL(访问控制列表)方式设置权限,使用ACL可以单独为每个用户设置不同的权限,而不是为三类用户设置权限,但并非所有的文件系统都支持ACL功能。

2.写权限,表示可以向文件写入数据、删除数据、修改数据,目录写权限表示可以在目录中新建文件、删除文件、修改文件名。

3.执行权限,普通文件的执行权限只针对可执行文件,目录文件的执行权限表示用户可以将此目录设置为工作目录。

以上权限产生的限制对root用户不生效。

文件权限除了以上三个基础权限外,还有很多特殊权限,比如s、t、i、a权限。

s权限建立在执行权限之上,文件需要首先拥有执行权限才能添加s权限。

此限制对root用户不生效。

操作系统的多用户功能对普通用户来说不太常用,甚至很多普通计算机用户不知道操作系统可以设置多个用户,但是对于服务器用户来说多用户功能是必备的,服务器经常需要被多个用户同时使用、并且需要对不同用户设置不同的使用权限。

用户不再使用计算机,退出操作系统的身份认证,称为退出、或者登出。

编号1-999的用户称为系统服务用户,每个daemon都使用一个独立的用户去运行,从而防止daemon因自身漏洞导致被人恶意利用,此类用户无需登录。

编号1000及以上的用户为普通用户。

用户必须加入用户组才能使用,并且一个用户可以同时加入多个用户组,若创建用户时不指定其加入的用户组,则操作系统会自动创建一个用户组并加入,新建用户组的名称与用户的名称相同。

/etc/shadow,存储用户密码,密码数据经过加密,并非使用明文存储,此文件只能被高级别用户访问,普通用户没有访问权利。

控制台是一种简单的IO设备,用于提供基础功能的控制开关、告知用户工作状态,这很像现在的自动洗衣机控制面板,用户通过按键发出数据控制洗衣机运行所需功能,洗衣机通过点亮不同的指示灯告知用户工作状态。

终端是一种复杂的IO设备,用于向CPU输入字符编码、接收CPU发出的数据,早期的终端使用穿孔纸带存储数据并向CPU输入,使用打印机在纸张上打印字符输出数据,之后的终端开始使用键盘和显示器输入输出数据。

计算机有多用户同时使用的需求,每个用户都需要对计算机进行输入输出数据操作,此时每个计算机用户都需要对计算机连接一终端,一台计算机可以连接多个终端,每个终端供一个用户使用,用户可以在这里进行登录,之后开始使用计算机,多个终端也可以同时登录同一个用户,若一个用户需要同时执行多个程序、并且每个程序都需要使用终端进行数据输入输出,就需要在多个终端登录同一个用户,之后在多个终端分别执行程序。

现在的计算机直接集成一套完整的基础IO设备,已经没有之前的控制台和终端,但是计算机依然有多用户同时使用的需求(工作站、服务器),依然需要多个用户同时进行数据的输入输出,为此linux使用一个程序模拟硬件终端,linux启动后默认创建7个终端进程,模拟7个硬件终端设备,用户可以使用 ALT +(F1 到 F7) 组合键切换不同的终端使用,当用户通过网络连接服务器进行登录时,用户自己携带的计算机就相当于服务器的硬件终端。

虽然现在的终端已经不再是硬件IO设备,而是变成了一个进程,但是linux依然使用设备文件的方式管理终端,而不是使用通信文件,终端设备文件的名称以tty开头,据传这是teletype的缩写。

tty1 - tty6 是以字符显示的终端,tty7是以图形显示的终端。

在终端内输入程序路径时可以使用相对路径,相对路径与PATH环境变量中记录的路径组合为完整路径,若相对路径需要与终端工作目录组合为完整路径,需要在相对路径中使用.符号,示例:https://www.bilibili.com/read/a.out。

C语言规定程序的main函数可以设置两个参数,参数的类型和名称是固定的,原型如下:

参数argc,存储用户在终端输入的字符串个数,用户输入的数据以空格或“”符号分割为多个字符串,注意:用户输入的程序路径也算入其中,所以argc的值最小为1。

参数argv,用户在终端输入的所有字符串放在一个数组中,相当于一个二维数组,argv指向此二维数组,二维数组的第一个字符串是输入的程序路径,剩余字符串是程序参数,末尾字符串为0。

argv参数还可以定义为如下形式:char argv,数组在传参时会转换为指针,二维数组转换为双层指针。

多数编译器还支持main添加第三个参数,如下:

参数envp指向本进程所用的环境列表,环境列表的有效元素之后也是存储一个0。

也可以使用编译器系统定义的全局变量environ查询环境列表。

执行程序时可以设置程序以后台进程方式运行,示例:https://www.bilibili.com/read/a.out &,在命令之后添加空格和 & 符号表示进程以后台方式运行,注意&符号并不是传递给程序的参数,而是传递给终端的指令。

ctrl + c

ctrl +

ctrl + z

在终端输入的某些命令没有对应的程序,这种命令是直接传输给shell的,由shell负责执行命令表示的功能,这种命令称为shell内部命令,比如修改终端工作目录的cd命令,shell接收到此命令后执行自身内部代码修改终端工作目录。

在终端输入的命令可以使用一些特殊符号参数,这些参数会被shell解释为对应的功能,常用符号如下:

———— 通配符 ————

符号,表示任意类型、任意数量(包括0个)的字符,比如.c表示任意以.c结尾的字符串,包括名称只有.c的字符串,比如a*.c表示任意以a开头、并以.c结尾的字符串,比如*ali表示任意包含ali单词的字符串。

?符号,表示任意类型的一个字符,比如a?b,表示以a开头、b结尾、中间任意类型单个字符的字符串,比如a??b,表示以a开头、b结尾、中间任意类型两个字符的字符串,比如?x,表示任意单个字符开始、并以x结尾的字符串。

[]符号,表示[]符号内指定单个字符的任意一个,类似或运算,比如a[1,2,x]b,表示a1b、a2b、axb,也可以指定字母或数字的范围,比如[0-9]表示任意单个数字,[a-z]表示任意单个小写字母,[A-Z]表示任意单个大写字母,[a-Z]表示不区分大小写单个字母。

———- 多命令执行 ———–

;符号,执行多个命令,不同命令使用;符号分割。

&&符号,分割多个命令,之前的命令解释成功时才会执行之后的命令。

||符号,分割多个命令,之前的命令解释失败时才会执行之后的命令。

——— 输入输出重定向 ———

>符号,将命令的标准输出存储到指定文件内(以覆盖数据方式写入),标准输出不会显示在终端。

>>符号,将命令的标准输出存储到指定文件内(以增加数据方式写入)。

2>符号,将命令的标准错误存储到指定文件内(以覆盖数据方式写入,若没有标准错误数据则只进行清空文件操作)。

2>>符号,将命令的标准错误存储到指定文件内(以增加数据方式写入,若没有标准错误数据则不对文件进行任何操作)。

&>符号,将命令的标准输出、标准错误存储到指定文件内(以覆盖数据方式写入)。

&>>符号,将命令的标准输出、标准错误存储到指定文件内(以增加数据方式写入)。

<符号,读取指定文件中的数据作为标准输入数据。

———— 管道符 ————

|符号,用于分割两个命令,同时将第一个命令执行完毕后在终端输出的数据作为第二个命令的输入数据。


通配符源于正则表达式,用于制定字符串的查询规则,shell会根据这些规则将查询到的字符串添加到命令中,这里介绍的只是通配符在shell中的使用方式,在其它地方的使用规则不一定完全相同。


下面制作一个删除文件的程序,并编译为 /delete.out。



此程序使用参数接收要删除文件的路径,若需要删除一个目录中指定类型的文件,无需用户手动查询,也无需程序自己查询,使用通配符让shell进行查询即可。

示例:/delete.out ~/text/.txt

此命令表示执行/delete.out程序,并由shell查询/text/目录中所有名称以.txt结尾的文件,之后将符合条件的文件路径输入到~/delete.out程序的参数。

输入重定向用于读取一个文件中的数据写入到进程使用的标准输入文件内。

控制台程序,使用终端进行数据输入输出,用户在终端输入字符控制程序执行,程序功能执行完毕后向终端输出字符告知执行结果,这种方式使用不方便,为了更方便的让程序与用户进行数据交流从而有了图形界面程序。

图形界面程序,程序在显示器中显示一个或多个独立的图形,这个图形称为图形界面(GUI),界面内部可以继续嵌套其它图形,嵌套的图形称为控件,控件还可以继续嵌套子控件,界面或控件都可以进行数据的输入输出,输出数据时可以显示字符、图片、动画,数据输出方式更灵活,输入数据时可以使用鼠标移动界面内的指针,从而选择要输入数据的界面,鼠标输入的数据常用于启动、终止程序的某些功能,控件可以与程序功能进行绑定,之后通过鼠标向控件输入数据启动与此控件绑定的功能。

图形界面程序不区分前台进程与后台进程,每个程序界面都可以输入输出数据,不会有控制台程序争抢终端使用权的情况,但是同一时间接收数据的图形界面只能有一个,鼠标输入的数据通过指针的位置确定要发送给哪个界面,键盘输入的数据需要操作系统设置哪个界面进行接收,这个接收数据的界面称为活动界面,有些数据是发送给控件的,所以界面内的控件也区分活动控件,活动界面可以通过鼠标移动指针到指定界面后按鼠标左键选择,也可以使用键盘中的 alt + tab 组合键在所有的界面中选择,界面内的控件使用tab键选择活动控件。

在拥有桌面子系统的操作系统中,用户登录后启动的第一个界面称为桌面,桌面占用整个屏幕,Windows的桌面大部分区域被一个容器控件占用,这个容器控件与用户主目录绑定,并使用子控件的方式显示目录内的文件,deepin的DDE桌面也是类似的设计。

Linux的桌面子系统不与系统内核绑定,不属于内核的一部分,而是在内核中运行的用户程序,桌面系统因故障终止运行后,操作系统依然可以切换到其它终端继续正常使用(在deepin中使用 ctrl + alt + f1至f7 切换内核提供的7个终端),而Windows的桌面系统是系统内核的一部分,桌面系统因故障终止运行后,操作系统也会随之终止。

常见的Linux桌面系统有:

GNU项目的GNOME,基于GTK开发。

开源社区的KDE,基于QT开发。

deepin的DDE,基于QT开发。

daemon与普通程序的区别在于运行方式,daemon需要与操作系统一同启动,系统内核启动后会自动调用daemon运行,即使没有用户登录也会运行,用户可以自己设计一种系统服务,并制作一个daemon实现此服务的功能,之后将daemon设置为自动启动,具体方式是在/etc/rc[0-6].d/目录中创建shell脚本文件并编写脚本代码实现,这里不介绍脚本文件的编写方式。

linux的桌面子系统实际上就是一个系统服务,这个系统服务一般在终端1或终端7中执行。

小讯
上一篇 2025-04-30 11:02
下一篇 2025-06-06 16:36

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/176819.html