Linux标准与系统结构详解
1. Linux初始化脚本标准
在Linux系统中,LSB 3.1规定初始化脚本的位置为
/etc/init.d
,不过也允许将其设置为指向其他位置的链接。
/etc/init.d
中的每个脚本都有一个与它所提供的服务相关的名称。由于这是所有Linux服务共享的公共命名空间,因此名称的唯一性非常重要。例如,如果MySQL和PostgreSQL都将它们的脚本命名为“database”,就会引发冲突。为避免这种情况,有另一个标准组织——The Linux Assigned Names And Numbers Authority(LANANA),其官网为http://www.lanana.org/ 。它维护着一个脚本和软件包的注册名称列表,为Linux系统的用户提供了便利。
初始化脚本必须接受一个参数来控制其操作,定义的参数及其含义如下表所示:
| 参数 | 含义 |
| — | — |
| start | 启动(或重启)服务 |
| stop | 停止服务 |
| restart | 重启服务,通常先停止服务,然后再启动 |
| reload | 重置服务,重新加载参数,但不停止服务。并非所有服务都支持此选项,所以有些脚本可能不接受该参数,或者接受但无实际效果 |
| force - reload | 如果服务支持,则尝试重新加载;否则,重启服务 |
| status | 打印服务状态的文本消息,并返回一个状态码,用于确定服务的状态 |
所有命令执行成功时返回0,失败时返回一个表示失败原因的错误代码。对于
status
命令,如果服务正在运行则返回0,其他代码表示服务因某种原因未运行。
2. 文件系统层次结构标准(FHS)
FHS(Filesystem Hierarchy Standard)的目的是定义Linux文件系统中的标准位置,让开发者和用户能够合理预期在哪里找到所需的内容。长期使用类UNIX操作系统的用户一直抱怨不同系统文件系统布局的细微差异,而FHS为Linux发行版提供了一种避免碎片化的方式。其官网为http://www.pathname.com/fhs/ 。
Linux系统中的文件布局最初看似是基于历史实践的半随意排列,但随着时间的推移,它已经演变成了我们现在看到的层次结构。总体思路是将文件和目录分为三组:
- 特定Linux系统独有的文件和目录,如启动脚本和配置文件。
- 只读且可在不同Linux系统之间共享的文件和目录,如应用程序可执行文件。
- 可读写且可在Linux系统或其他操作系统之间共享的目录,如用户主目录。
FHS定义的顶级结构包含几个必需的子目录和少量可选目录,主要的目录及其用途如下表所示:
| 目录 | 是否必需 | 用途 |
| — | — | — |
| /bin | 是 | 重要的系统二进制文件 |
| /boot | 是 | 系统启动所需的文件 |
| /dev | 是 | 设备文件 |
| /etc | 是 | 系统配置文件 |
| /home | 否 | 用户文件目录 |
| /lib | 是 | 标准库 |
| /media | 是 | 可移动媒体的挂载点,为系统支持的每种媒体类型提供单独的子目录 |
| /mnt | 是 | 临时挂载设备(如CD - ROM和闪存盘)的便捷位置 |
| /opt | 是 | 额外的应用程序软件 |
| /root | 否 | 根用户的文件 |
| /sbin | 是 | 系统启动期间必需的重要系统二进制文件 |
| /srv | 是 | 此系统提供的服务的只读数据 |
| /tmp | 是 | 临时文件 |
| /usr | 是 | 二级层次结构。传统上也存储用户文件,但现在不建议普通用户对其进行写操作 |
| /var | 是 | 可变数据,如日志文件 |
此外,还可能存在以
lib
开头的其他目录,但这种情况并不常见。通常还会看到
/lost+found
目录(用于文件系统恢复)和
/proc
目录,它是一个伪文件系统,提供对当前运行系统的映射。当前版本的FHS标准强烈建议存在
/proc
文件系统,但并非必需。
下面简要介绍根目录下各标准子目录的用途:
-
/bin
:包含根用户和普通用户都可使用的二进制文件,在单用户模式下,当其他一些目录结构可能未挂载时,这些文件对系统运行至关重要。例如,
cat
、
ls
和
sh
等核心命令通常位于此目录。
-
/boot
:用于存放Linux系统启动所需的文件。该目录通常较小,小于100 MB,并且常作为一个单独的分区。在基于PC的系统中,BIOS对活动分区有一定限制,要求其位于磁盘的前2G或4G内,将
/boot
作为单独分区可以在规划磁盘分区时提供更多灵活性。
-
/dev
:包含映射到硬件的特殊设备文件。例如,
/dev/hda
将映射到第一个IDE磁盘。
-
/etc
:包含系统配置文件。过去,这里也可能有一些二进制文件,但在大多数Linux系统中已不再如此。
/etc
目录中最著名的文件可能是
passwd
,它包含用户信息。其他有用的文件包括
fstab
(列出挂载选项)、
hosts
(列出IP到主机名的映射)以及
httpd
目录(包含Apache服务器的配置)。
-
/home
:用于存放用户文件。通常,每个用户在该目录下都有一个与他们登录名相同的目录,这将是他们的默认登录目录。例如,用户
rick
登录后,很可能会进入
/home/rick
目录。
-
/lib
:包含必需的共享库和内核模块,特别是系统启动或单用户模式下所需的那些。
-
/media
:作为顶级目录,用于包含其他可移动媒体的挂载点目录。其目的是减少不必要的顶级目录,如
/cdrom
和
/floppy
。
-
/mnt
:只是一个临时挂载额外文件系统的便捷位置。过去,一些发行版在
/mnt
下为不同设备添加了子目录,如
/cdrom
和
/floppy
,但现在建议将这些目录放在
/media
下,使
/mnt
恢复其作为单一顶级临时挂载位置的原始用途。
-
/opt
:供软件供应商在基础发行版中添加额外软件应用程序时使用。发行版不应将其用于标准发行版中的软件,而应留给第三方供应商使用。通常,供应商会创建一个以其名称命名的子目录,然后再创建
/bin
和
/lib
等子目录,用于存放特定于其应用程序的文件。按照惯例,许多开源Linux软件包使用
/usr/local
进行安装。
-
/root
:用于存放根用户的文件。它不在
/home
目录树中,因为在单用户模式下,
/home
可能未挂载。
-
/sbin
:用于存放通常仅由系统管理员使用的命令,以及系统启动或单用户模式下必需的命令,如
fsck
、
halt
和
swapon
。
-
/srv
:用于存放特定站点的只读配置数据,但目前并不常用。
-
/tmp
:用于存放临时文件。系统启动时,该目录通常(但不总是)会被清空。
-
/usr
:是一个较为复杂的二级文件系统,通常包含系统启动或单用户模式下不需要的所有系统命令和库。它有许多子目录,如
/bin
、
/lib
、
/X11R6
和
/local
。早期的UNIX和Linux系统中,
/usr
还包含日志、邮件假脱机等子目录,现在这些都已移到
/var
目录。这样做的好处是,
/usr
现在可以作为一个可挂载的文件系统,并且大多数时候可以以只读方式挂载。当
/usr
以只读方式挂载时,它可以通过网络共享给其他系统,并且在系统因电源故障等原因意外停止时,不太容易损坏。
-
/var
:包含经常变化的数据,如打印假脱机文件、应用程序日志文件和邮件假脱机目录。
下面是一个简单的mermaid流程图,展示了FHS目录结构的主要部分:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A[/]:::process --> B[/bin]:::process
A --> C[/boot]:::process
A --> D[/dev]:::process
A --> E[/etc]:::process
A --> F[/home]:::process
A --> G[/lib]:::process
A --> H[/media]:::process
A --> I[/mnt]:::process
A --> J[/opt]:::process
A --> K[/root]:::process
A --> L[/sbin]:::process
A --> M[/srv]:::process
A --> N[/tmp]:::process
A --> O[/usr]:::process
A --> P[/var]:::process
3. 编写和部署可移植Linux应用程序的其他考虑因素
如果要编写和部署完全可移植的Linux应用程序,还有许多其他因素需要考虑。
3.1 本地化
如果你想让应用程序支持不同的语言和区域设置,即使只使用英语,也会涉及货币、数字分隔符、日期格式等问题。有相关人员正在制定这些标准,你可以在http://www.openi18n.org/ 查看他们的工作。
3.2 目标系统的安装情况
另一个需要考虑的问题是目标系统安装了哪些选项、库版本等。幸运的是,由于前面提到的标准化工作,这个问题的严重性正在降低,但仍然可能是一个难题。有一对GNU工具
autoconf
和
automake
可以在很大程度上帮助解决这个问题。虽然你可能没有直接使用过它们,但在从源代码安装软件时,输入
./configure; make
,你几乎肯定已经受益于它们了。你可以在GNU网页http://www.gnu.org/software/autoconf/ 和http://www.gnu.org/software/automake 上找到更多关于它们的信息。
遵循这些标准有助于使Linux成为一个更易于编程的平台,并确保不同的Linux发行版符合一些基本标准,让程序员和用户的生活更加轻松。我们鼓励大家使用这些标准,并鼓励他人也这样做。
Linux标准与系统结构详解(续)
4. 系统初始化与脚本参数
在Linux系统中,系统初始化脚本起着至关重要的作用。正如前面提到的,LSB 3.1规定初始化脚本位于
/etc/init.d
,且脚本名称需与服务相关且唯一。下面详细介绍初始化脚本参数的操作及相关注意事项。
当执行初始化脚本时,不同的参数会触发不同的操作,具体操作流程如下:
1.
启动服务(start)
:该参数用于启动或重启服务。当执行
start
参数时,脚本会检查服务当前状态,如果服务未运行,则启动服务;如果服务已运行,则可能会先停止服务,然后再重新启动。例如,对于一个名为
my_service
的服务,启动命令为
/etc/init.d/my_service start
。
2.
停止服务(stop)
:此参数用于停止正在运行的服务。执行
stop
参数时,脚本会向服务发送停止信号,让服务正常关闭。如
/etc/init.d/my_service stop
。
3.
重启服务(restart)
:通常是先执行
stop
操作,再执行
start
操作。这样可以确保服务以全新的状态运行。命令为
/etc/init.d/my_service restart
。
4.
重新加载参数(reload)
:尝试在不停止服务的情况下,重置服务并重新加载参数。但并非所有服务都支持此操作,若服务不支持,该参数可能无效。使用方式为
/etc/init.d/my_service reload
。
5.
强制重新加载(force - reload)
:如果服务支持
reload
操作,则执行
reload
;若不支持,则执行
restart
。命令是
/etc/init.d/my_service force - reload
。
6.
查看服务状态(status)
:打印服务状态的文本消息,并返回一个状态码。若返回0,表示服务正在运行;其他代码表示服务未运行。例如
/etc/init.d/my_service status
。
所有这些命令执行成功时返回0,失败时返回相应的错误代码,方便用户判断操作结果。
5. 文件系统层次结构的深入理解
FHS定义的文件系统层次结构为Linux系统的组织和管理提供了标准。下面进一步分析各子目录的特点和使用场景。
5.1 /usr目录
/usr
目录是一个复杂的二级文件系统,包含了大量系统启动或单用户模式下不需要的系统命令和库。它的子目录众多,如
/usr/bin
包含了许多用户级别的可执行文件,
/usr/lib
存放了各种库文件。在早期,
/usr
还用于存储用户文件,但现在这种做法已不被推荐,普通用户不应向
/usr
目录写入数据。
例如,当安装一个新的软件包时,很多时候软件的可执行文件会被安装到
/usr/bin
或
/usr/local/bin
,库文件会被安装到
/usr/lib
或
/usr/local/lib
。这样的布局使得系统的软件管理更加规范。
5.2 /var目录
/var
目录用于存放可变数据,如日志文件、邮件假脱机目录等。这些数据会随着系统的运行而不断变化。例如,系统日志文件通常存放在
/var/log
目录下,不同的服务会有各自的日志文件,如
/var/log/messages
记录了系统的一般消息,
/var/log/secure
记录了安全相关的信息。
邮件假脱机目录
/var/spool/mail
用于存放用户的邮件,当有新邮件到达时,会被存储在该目录下对应的用户邮箱中。
下面是一个表格,总结了
/usr
和
/var
目录的主要特点:
| 目录 | 特点 | 用途示例 |
| — | — | — |
| /usr | 二级层次结构,包含系统命令和库,一般只读 | 安装软件的可执行文件和库文件 |
| /var | 存放可变数据 | 日志文件、邮件假脱机目录 |
6. 内存管理与调试
在Linux系统中,内存管理是一个重要的方面。了解内存的分配、使用和调试方法,有助于提高程序的性能和稳定性。
6.1 内存分配
内存分配主要通过
malloc
、
calloc
和
realloc
等函数实现。
malloc
用于分配指定大小的内存块,例如:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
ptr = (int *)malloc(10 * sizeof(int));
if (ptr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用分配的内存
for (int i = 0; i < 10; i++) {
ptr[i] = i;
}
// 释放内存
free(ptr);
return 0;
}
calloc
在分配内存的同时会将内存块初始化为0,
realloc
用于调整已分配内存块的大小。
6.2 内存调试
内存调试是解决程序中内存问题的重要手段。常见的内存问题包括内存泄漏、野指针等。有一些工具可以帮助进行内存调试,如
valgrind
和
ElectricFence
。
valgrind
是一个强大的内存调试工具,可以检测内存泄漏、越界访问等问题。使用
valgrind
调试程序的步骤如下:
1. 编译程序时确保使用了调试信息,例如
gcc -g -o my_program my_program.c
。
2. 使用
valgrind
运行程序,如
valgrind --leak-check=full ./my_program
。
3.
valgrind
会输出详细的内存使用信息和可能存在的问题。
ElectricFence
则是一个简单的内存保护工具,它可以帮助检测内存越界访问。使用时,需要在编译程序时链接
ElectricFence
库,例如
gcc -o my_program my_program.c -lefence
。
7. 进程与线程管理
Linux系统中的进程和线程是实现多任务处理的关键。了解它们的创建、调度和同步机制,有助于编写高效的程序。
7.1 进程管理
进程是程序在操作系统中的一次执行实例。创建进程主要通过
fork
和
exec
系列函数实现。
fork
用于创建一个新的子进程,子进程是父进程的副本。例如:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
printf("进程创建失败\n");
return 1;
} else if (pid == 0) {
// 子进程
printf("这是子进程\n");
} else {
// 父进程
printf("这是父进程,子进程ID为 %d\n", pid);
}
return 0;
}
exec
系列函数用于替换当前进程的映像,执行一个新的程序。例如
execlp
函数:
#include <stdio.h>
#include <unistd.h>
int main() {
execlp("ls", "ls", "-l", NULL);
printf("如果执行到这里,说明execlp调用失败\n");
return 1;
}
进程的调度由操作系统负责,根据进程的优先级和资源需求进行合理分配。用户可以使用
nice
和
renice
命令调整进程的优先级。
7.2 线程管理
线程是进程内的一个执行单元,多个线程可以在同一个进程内并发执行。在Linux中,使用POSIX线程库(pthread)来创建和管理线程。
创建线程的基本步骤如下:
1. 包含
pthread.h
头文件。
2. 定义线程函数。
3. 使用
pthread_create
函数创建线程。
以下是一个简单的线程示例:
#include <stdio.h>
#include <pthread.h>
void *thread_function(void *arg) {
printf("这是一个线程\n");
return NULL;
}
int main() {
pthread_t thread_id;
int result = pthread_create(&thread_id, NULL, thread_function, NULL);
if (result != 0) {
printf("线程创建失败\n");
return 1;
}
pthread_join(thread_id, NULL);
printf("主线程结束\n");
return 0;
}
线程的同步可以使用互斥锁(mutex)和信号量(semaphore)等机制,确保多个线程安全地访问共享资源。
8. 网络编程与套接字
网络编程是Linux系统中实现网络通信的重要手段。套接字(socket)是网络编程的基础,通过套接字可以实现不同主机之间的通信。
8.1 套接字的基本概念
套接字是一种网络通信的抽象接口,它具有不同的属性,包括域(domain)、类型(type)和协议(protocol)。常见的套接字域有
AF_INET
(IPv4)和
AF_UNIX
(本地套接字),类型有
SOCK_STREAM
(面向连接的流式套接字)和
SOCK_DGRAM
(无连接的数据报套接字)。
创建套接字的基本步骤如下:
1. 使用
socket
函数创建套接字。
2. 绑定套接字地址(可选)。
3. 监听连接(对于服务器端)或发起连接(对于客户端)。
4. 进行数据的发送和接收。
以下是一个简单的TCP服务器和客户端示例:
服务器端代码 :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
const char *hello = "Hello from server";
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字地址
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 读取客户端发送的数据
read(new_socket, buffer, 1024);
printf("客户端消息: %s\n", buffer);
// 发送响应消息
send(new_socket, hello, strlen(hello), 0);
printf("消息已发送\n");
// 关闭套接字
close(new_socket);
close(server_fd);
return 0;
}
客户端代码 :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[1024] = {0};
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation error");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 将IPv4地址从文本转换为二进制形式
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
return -1;
}
// 发送消息
send(sock, hello, strlen(hello), 0);
printf("消息已发送\n");
// 读取服务器响应
read(sock, buffer, 1024);
printf("服务器响应: %s\n", buffer);
// 关闭套接字
close(sock);
return 0;
}
通过以上代码,客户端和服务器可以进行简单的TCP通信。
9. 总结
Linux系统拥有丰富的标准和机制,从系统初始化脚本到文件系统层次结构,从内存管理到网络编程,每个方面都有其独特的特点和使用方法。遵循这些标准和机制,有助于提高系统的可维护性、可移植性和性能。无论是开发者还是系统管理员,都应该深入了解这些知识,以便更好地使用和管理Linux系统。同时,不断学习和掌握新的技术和方法,能够让我们在Linux的世界中更加得心应手。
下面是一个mermaid流程图,展示了网络编程的基本流程:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A[创建套接字]:::process --> B[绑定地址(服务器端)]:::process
B --> C[监听连接(服务器端)]:::process
C --> D[接受连接(服务器端)]:::process
A --> E[发起连接(客户端)]:::process
D --> F[数据收发]:::process
E --> F
F --> G[关闭套接字]:::process
通过对这些知识的学习和实践,我们可以更好地利用Linux系统的强大功能,开发出高效、稳定的应用程序。
超级会员免费看

被折叠的 条评论
为什么被折叠?



