其他
# 操作系统相关
# 冯诺伊曼体系
该思想约定了用二进制进行计算和存储,还定义计算机基本结构为 5 个部分,分别是中央处理器(CPU)、内存、输入设备、输出设备、总线。
# 文件相关
# 文件组成
以Linux系统为例,在Linux系统中一切皆文件,Linux文件系统会为每个文件分配索引节点 inode
跟目录项directory entry
来记录文件内容跟目录层次结构。
# 文件类型
划分文件类型的目的是为了更好地管理文件,按照不同的分类,可以将文件类型进行不同的划分
- 按照用途划分
- 系统文件
- 用户文件
- 库文件
- 按照文件中数据的形式划分
- 源文件
- 目标文件
- 可执行文件
- 按照存取控制权限划分
- 只执行文件
- 只读文件
- 读写文件
- 按照组织形式和处理方式划分
- 普通文件
- 二进制文件
- 特殊文件
操作系统基础之文件管理 - 简书 (jianshu.com) (opens new window)
# 文件和目录的区别
文件权限一般可认为是0 123 456 789,一共十位,0:表示该文件的文件类型。在Linux中文件类型只有以下这几种:
- -,普通文件。
- d,目录文件,d是directory的简写。
- l,软连接文件,亦称符号链接文件,s是soft或者symbolic的简写。
- b,块文件,是设备文件的一种(还有另一种),b是block的简写。
- c,字符文件,也是设备文件的一种(这就是第二种),c是character的文件。
Linux系统最原始的也只有这五种,所以第0位,只能是以上五者之一。
为什么硬连接没有类型表示?答案:硬连接和软连接,名字上虽然只差一个字,本质完全不同,硬连接也是文件。其类型是普通文件。
那么我们如何找到某个文件呢?使用inode号,我们的目录本质上就是一张表,里面会存储Innodb和文件的映射关系,比如下面这种的。
体现在本质上
普通文件:存储普通数据,一般就是字符串。 目录文件:存储了一张表,该表就是该目录文件下,所有文件名和inode的映射关系。 从父目录中获得本文件的inode号---->找到inode-table表中找到这个inode号对应的数据域中的起点以及其他信息---->去这个数据域中读取该文件的内容(普通文件的内容一般是字符串,目录文件的内容是一张表)
体现在命令上:
对于普通文件来说,rwx的意义是: r:可以获得这个普通文件的名字和内容。 w:可以修改这个文件的内容和文件名。可以删除该文件,但是用户会得到是否删除写保护文件的prompt。
x:该文件是否具有被执行的权限。
对于目录文件来说,rwx的意义是: r-x:可以进入cd该目录,可以获得该目录下存储情况,但是不能修改这个目录内部存储的文件(目录)的名字,也不能在该目录下新建文件和目录 -wx:可以进入cd该目录,但是看不到该目录下的存储情况(ls不可用),可以往该目录下添加、修改、删除文件。可以通过cat来读取该目录下的文件or目录的内容,由于得不到该目录下存储了那些文件,在不知情的情况下只能通过猜,cat + 文件名获得文件内容,所以这样依然不保密。 --x:可以进入cd该目录,看不到存储情况,也不能往该目录下添加、修改、删除文件。但是依然可以通过cat + xx(猜)来获得该目录下的文件的内容。
rw-:不能进入cd该目录,用ls仅仅可以获得文件名和目录名,因为获取不到这些文件的inode号,当然也不能获得该目录下的文件的内容。不能往该目录下添加、修改、删除文件。
最后总结一下吧: 1.目录文件虽然是文件(唉,谁叫Linux的核心理念就是Everything is file),但是存储内容的只是一张表而已,关于文件名和inode号的映射关系。 2.文件的扩展名和文件类型之间,没一毛钱关系。 3.文件的文件名和文件实际存储内容之间,没一毛钱关系。 4.要知道如何查找到一个文件内容的过程。 5.为什么同一个文件系统移动文件要比跨文件系统快?
答:因为只需要修改某个目录中路径和inode对应关系即可,不需要重新写一遍数据域。 6.什么是买来的500G的硬盘,格式化完后总是少了达不到500G?
答:从本文可知,inode-table也是需要占用存储空间的,所以缺少的一部分中inode-table占用了不少。
linux的文件和目录的区别和联系_Faith的博客-CSDN博客_linux文件和目录的区别 (opens new window)
# CAP理论
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
# 一致性(Consistency)
一致性指“all nodes see the same data at the same time
”,即所有节点在同一时间的数据完全一致。
一致性是因为多个数据拷贝下并发读写才有的问题,因此理解时一定要注意结合考虑多个数据拷贝下并发读写的场景。
# 可用性(Availability)
可用性指“Reads and writes always succeed
”,即服务在正常响应时间内一直可用。
好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。可用性通常情况下可用性和分布式数据冗余,负载均衡等有着很大的关联。
# 分区容错性(Partition tolerance)
分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system
”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。
# CAP权衡
CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但其实分区不是你想不想的问题,而是始终会存在,因此CA的系统更多的是允许分区后各子系统依然保持CA。 CP without A:如果不要求A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。 AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。
对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。
对于涉及到钱财这样不能有一丝让步的场景,C必须保证。网络发生故障宁可停止服务,这是保证CA,舍弃P。貌似这几年国内银行业发生了不下10起事故,但影响面不大,报道也不多,广大群众知道的少。还有一种是保证CP,舍弃A。例如网络故障事只读不写。
孰优孰略,没有定论,只能根据场景定夺,适合的才是最好的。
参考:
谈谈分布式系统的CAP理论 - 知乎 (zhihu.com) (opens new window)
# 系统调用
Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。
随Linux核心还提供了一些C语言函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。
主要分为以下几大类
进程控制
文件系统控制
- 文件读写
- 文件系统操作
系统控制
内存管理
网络管理
socket控制
用户管理
进程间通信
- 信号
- 消息
- 管道
- 信号量
- 共享内存
Linux系统调用详解(实现机制分析)--linux内核剖析(六)_OSKernelLAB-CSDN博客_linux系统调用 (opens new window)
什么是系统调用?为什么要用系统调用? - 华为云 (huaweicloud.com) (opens new window)
库函数调用和系统调用的区别
函数库调用 | 系统调用 |
---|---|
与用户程序相联系 | 是操作系统的一个入口点 |
在用户地址空间执行 | 在内核地址空间执行 |
它的运行时间属于“用户时间” | 它的运行时间属于“系统”时间 |
# Socket编程(网络编程)
基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下:
# socket()函数
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而 socket() 用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。函数定义如下:
int socket(int domain, int type, int protocol);
- domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
- type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
- protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
# bind()函数
正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
- addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同
- addrlen:对应的是地址的长度。
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
# connect()函数
对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手 (opens new window),而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手 (opens new window)连接(三次握手详情,请看《浅谈 TCP 三次握手》 (opens new window)),最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。
通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手 (opens new window)成功或超时失败才返回(正常的情况,这个过程很快完成)。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
# listen()函数
对于服务器,它是被动连接的。举一个生活中的例子,通常的情况下,移动的客服(相当于服务器)是等待着客户(相当于客户端)电话的到来。而这个过程,需要调用listen()函数。
#include<sys/socket.h>
int listen(int sockfd, int backlog);
2
listen() 函数的主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度, TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。
第二个参数( backlog)的作用:告诉内核连接队列的长度。
为了更好的理解 backlog 参数,我们必须认识到内核为任何一个给定的监听套接口维护两个队列:
1、未完成连接队列(incomplete connection queue),每个这样的 SYN 分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手 (opens new window)过程。这些套接口处于 SYN_RCVD 状态。
2、已完成连接队列(completed connection queue),每个已完成 TCP 三次握手 (opens new window)过程的客户对应其中一项。这些套接口处于 ESTABLISHED 状态。
当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项,然后响应以三次握手的第二个分节:服务器的 SYN 响应,其中稍带对客户 SYN 的 ACK(即SYN+ACK),这一项一直保留在未完成连接队列中,直到三次握手的第三个分节(客户对服务器 SYN 的 ACK )到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。
如果三次握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。
这里需要注意的是 listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。
这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手 (opens new window),将建立好的链接自动存储到队列中,如此重复。
所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而 这个连接的过程是由内核完成。
# accept()函数
accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。
如果,服务器不能及时调用 accept() 取走队列中已完成的连接,队列满掉后会怎样呢?UNP(《unix网络编程》)告诉我们,服务器的连接队列满掉后,服务器不会对再对建立新连接的syn进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。但实际上Linux的并不是这样的!
TCP 的连接队列满后,Linux 不会如书中所说的拒绝连接,只是有些会延时连接,而且accept()未必能把已经建立好的连接全部取出来(如:当队列的长度指定为 0 ),写程序时服务器的 listen() 的第二个参数最好还是根据需要填写,写太大不好(具体可以看cat /proc/sys/net/core/somaxconn,默认最大值限制是 128),浪费资源,写太小也不好,延时建立连接。
# read()、write()等函数
万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
close函数
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
int close(int fd);
close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
TCP网络编程中connect()、listen()和accept()三者之间的关系_秋叶原 && Mike || 麦克-CSDN博客 (opens new window)
Linux Socket编程(不限Linux) - 吴秦 - 博客园 (cnblogs.com) (opens new window)
# 程序编译执行流程
编译流程
构建C程序需要4个步骤,分别使用4个工具完成: preprocessor, compiler, assembler, and linker.四步完成后生成一个可执行文件。
- 第一步,预处理. 这一步处理 头文件、条件编译指令和宏定义。
- 第二步,编译. 将第一步产生的文件连同其他源文件一起编译成汇编代码。
- 第三步,汇编。将第二步产生的汇编源码转换为 object file.
- 第四步,链接. 将第三步产生的一些object file 链接成一个可执行的文件。
执行流程
- 将编译后的程序加载到操作系统的执行内存中。
- 操作系统把加载到内存中的数据进行人为的分区,大致分为:
- .data区:常量区,存放程序中的所有静态常量,相当于java中的public static 的常量,在C语言中则是通过宏定义(define)声明的常量。
- .code区:方法区,存放funcation编译后的声明和实现的描述(其实也是0101).
- 栈(Stack)空间:程序运行时存放变量的空间,大小由操作系统指定,是一块连续的内存空间,访问速度和效率比Heap要高一些。
- 堆(Heap)空间:存放对象的一块不连续的内存空间,访问、存储效率比栈稍低。
- 划分好区域并将对应的数据加载到各自分区后,各个内存分区开始配合工作,举例:
参考: