一、Linux系统用户标识
在Linux中,每个用户都有一个唯一的标识符(User ID,UID)和一个组标识符(Group ID,GID)。UID用于标识单个用户,GID用于标识用户所属的组。
可以使用id
命令来查看当前用户的UID和GID,该命令将返回当前用户的UID、GID以及所属的其他附加组的ID。
要查看其他用户的UID和GID,可以使用以下命令:
id username
其中,username
是要查询的用户名。
另外,可以通过以下命令列出系统上所有用户及其UID和GID:
cat /etc/passwd
该命令将返回一个包含所有用户的行列表,每行包含用户名、加密密码、UID、GID以及其他有关用户的信息。
为什么要用UID标识用户,而不是使用用户名呢?
在Linux系统中,使用UID标识用户的原因是为了确保在系统级别上对用户进行唯一的标识,而不受用户名的更改或冲突的影响。
虽然用户名在人类使用时是很方便的,但是在计算机系统中,用户名是可变的。如果两个用户拥有相同的用户名,则会出现冲突,并且难以确定哪个用户拥有哪些权限和资源。另外,用户可以更改他们的用户名,这可能会破坏系统配置和授权。
相比之下,UID是一个不可变的标识符,它在用户创建时就分配给了他们,并且在用户更改其用户名时不会改变。这使得UID成为一个更可靠的标识符,可以确保对用户进行唯一的标识。
此外,在Linux系统中,UID还用于实现安全性。许多系统文件和资源只能由特定UID的用户访问。使用UID标识用户可以确保只有经过授权的用户才能访问这些资源,从而提高了系统的安全性。
二、Linux的进程
在Linux中,进程是一个正在执行的程序的实例。它是计算机系统中最基本的执行单元之一,每个进程都有它自己的一组资源,如内存、文件句柄、网络连接等。
2.1 进程的特性
进程具有以下特性:
- 独立性:每个进程都是独立的实体,它们的运行不会相互干扰。(fork出子进程,代码共享,数据各自私有一份)
- 资源管理:每个进程都有自己的一组资源,如内存、文件句柄、网络连接等。
- 并发性:在一个系统中可以同时运行多个进程,它们可以并发地执行。多个进程在一个cpu下采用进程切换的方式,在一段时间内使得多个进程推进。(进程切换时,保存进程上下文,每个进程开始在cpu上运行时,必须恢复上下文)
- 活跃性:进程可以在系统中创建、运行、挂起、恢复和终止。
- 通信机制:进程可以通过各种通信机制(如管道、消息队列、信号量等)来进行相互之间的通信和同步。
- 调度和优先级:进程可以由操作系统进行调度和优先级分配,以便更好地利用系统资源和提高系统的性能。
- 并行性:多个进程在多个cpu下同时运行。
- 竞争性:进程和资源之间,进程永远是多数。为了高效完成任务,更合理利用资源,便有了优先级。
2.2 利用系统监视器监测进程状态
在Linux中,任务管理器通常被称为系统监视器,你可以使用以下方法打开它:
- 使用快捷键:在大多数Linux发行版中,你可以按下Ctrl + Alt + Del键来打开系统监视器。
- 使用终端命令:打开终端窗口,输入命令top或htop,回车后会显示当前系统运行的进程列表,资源使用情况等信息。
- 使用桌面环境的菜单:大多数Linux桌面环境都提供了一个任务管理器程序,你可以在菜单中找到它并点击打开。
- 使用命令行命令:打开终端窗口,输入命令gnome-system-monitor或kde-system-monitor,回车后会启动系统监视器。
以上方法中,前两种方法比较常用,因为它们可以在大多数Linux系统上使用,而且使用简单方便。如果你使用的是图形界面,第三种方法也是一个很好的选择。如果你更喜欢使用命令行,那么第四种方法可能更适合你。
2.3 验证进程的独立性
2.3.1 kill一个进程不会影响其它进程
下面是一个简单的实验,用于验证Linux下进程的独立性:
- 打开终端窗口,输入命令"sleep 60 &",回车后会创建一个新的进程,在后台运行60秒。
- 在终端窗口中输入命令"ps -ef | grep sleep",回车后会显示所有运行中包含字符串"sleep"的进程列表。
- 记录下第2步中显示的进程ID(PID),假设为1234。
- 打开另一个终端窗口,输入命令"kill -9 1234",回车后会终止进程。
- 回到第2个终端窗口,重新输入命令"ps -ef | grep sleep",回车后会发现第4步中被终止的进程不再存在于进程列表中。
这个实验演示了Linux下进程的独立性,即使一个进程被终止,其他进程仍然可以继续运行,而且不会受到被终止进程的影响。
2.3.2 子进程异常退出,父进程不受影响
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
sleep(5);
int a = 1 / 0; // 除0错误,如果父进程不进行等待,子进程会成为僵尸进程
}
else
{
while (1)
{
printf("I am father!\n");
int status; // 用于存储子进程的退出状态
pid_t ret = wait(&status); // 返回子进程pid
if(ret > 0)
{
if(WIFEXITED(status))
{
printf("Child process %d exit with code %d\n", ret, WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("Child process %d exit due to signal %d\n", ret, WTERMSIG(status));
}
}
}
}
return 0;
}
当运行上述代码时,会创建一个父进程和一个子进程。在父进程中,使用一个无限循环来不断打印"I am father!",同时调用wait
函数等待子进程结束。在子进程中,先使用sleep
函数让进程睡眠5秒钟,然后故意发生除以0的错误,使进程异常退出。
在父进程中调用wait
函数等待子进程结束,有两种情况:
- 子进程正常结束。这种情况下,
wait
函数返回子进程的进程ID,并将子进程的退出状态保存在status
参数中。我们可以使用WIFEXITED
宏和WEXITSTATUS
宏分别判断子进程是否正常退出,并获取子进程的退出码。 - 子进程因为信号被杀死。这种情况下,
wait
函数返回子进程的进程ID,并将子进程的信号编号保存在status
参数中。我们可以使用WIFSIGNALED
宏和WTERMSIG
宏分别判断子进程是否因为信号被杀死,并获取子进程被杀死的信号编号。
无论子进程是正常退出还是因为信号被杀死,父进程都会根据相应的信息打印出一条消息,表明子进程的状态。这个过程会不断重复,直到父进程自己被杀死为止。
该子进程会收到8号信号而退出。Linux中的8号信号是SIGFPE
信号。它是指浮点异常信号,当程序执行浮点运算时,如果出现了异常情况,如除以零或无穷大等,就会触发该信号。当进程接收到SIGFPE
信号时,默认情况下会终止进程的执行。
总之,这段代码的目的是展示Linux下进程的独立性。父进程和子进程是独立运行的,彼此之间不受影响。同时,在父进程中对子进程进行了处理,避免了子进程成为僵尸进程。
三、环境变量
Linux环境变量是一种在操作系统中定义的全局变量,可以在所有的终端会话中使用。环境变量包含了一些系统和应用程序需要的重要信息,例如路径、用户名、系统语言等等。
Linux系统中有两种类型的环境变量:系统环境变量和用户环境变量。系统环境变量适用于整个系统,而用户环境变量只适用于当前登录的用户。
3.1 设置环境变量
在Linux中,可以使用export
命令来设置环境变量。例如,要将/usr/local/bin
添加到系统路径中,可以使用以下命令:
export PATH=$PATH:/usr/local/bin
这将把/usr/local/bin
添加到当前用户的路径中。如果要使此变量在每个终端会话中都可用,可以将该命令添加到~/.bashrc
文件中。如果要使其适用于所有用户,可以将该命令添加到/etc/profile
文件中。
3.2 打印环境变量
除了PATH
,Linux中还有许多其他常用的环境变量,例如HOME
,它指定了当前用户的主目录;LANG
,它指定了系统的默认语言;PWD
,它指定了当前工作目录的路径等等。可以使用echo
命令来查看当前设置的环境变量值。例如,要查看PATH
的值,可以使用以下命令:
echo $PATH
显示所有环境变量:
env
在Linux中,环境变量通常以字符串的形式存储,并以一个名称和一个值的组合表示。环境变量的名称通常都是大写字母,而值则可以是任何字符串。
3.3 PATH 环境变量详解
3.3.1 程序、命令、指令和可执行程序
它们在Linux可以是不同的东西,但对于PATH来说,可以认为都是同一概念。
举例来说,PATH环境变量用于存储系统中可执行程序的路径列表,当用户在命令行输入一个命令时,Linux会搜索这些路径来寻找该命令的可执行文件。因此,程序、命令、指令和可执行程序都可以使用PATH环境变量来实现在系统中的调用。
3.3.2 PATH的作用
为什么运行一个我们编译生成可执行程序要带./
,而比如ls
等命令就无需使用呢?
在Linux系统中,要运行一个可执行程序,需要在命令行中输入该程序的名称。但是,当程序位于当前工作目录中时,需要在程序名称前面添加"./“,否则Linux会在系统的环境变量PATH所指定的路径中搜索该程序,而不是在当前目录中查找。而对于一些Linux内置的命令,如"ls”,则不需要添加"./",因为它们已经被添加到了PATH环境变量中的默认搜索路径中。
需要注意的是,如果在程序名称前不加"./“,并且该程序也不在PATH环境变量所指定的默认搜索路径中,那么Linux会提示"command not found"错误。因此,在运行可执行程序时,为了确保Linux能够正确地找到该程序,应该使用”./"来指定程序的路径,或者将程序所在目录添加到PATH环境变量中。
下面是自定义PATH的方式:
(1)将可执行程序拷贝至PATH中的任何一个路径里即可。
(2)将当前可执行程序路径添加至PATH中。
PATH=$PATH:当前可执行程序路径
如果不小心执行了
PATH=/
会导致大部分命令失效,但是在系统重新启动时都会给PATH环境变量赋值的。
3.4 其它环境变量
3.4.1 HOME 环境变量
为什么普通用户和root刚登录时所处的家目录不同?
Linux环境变量HOME是指当前用户的家目录路径。在Linux系统中,每个用户都有一个家目录,它是该用户的默认工作目录。当用户登录时,系统会自动将该用户的工作目录设置为其家目录。
HOME环境变量通常被用于在脚本和程序中引用当前用户的家目录路径。可以通过在终端中输入"echo $HOME"来查看当前用户的家目录路径。
在Linux系统中,家目录通常被存储在"/home"目录下。例如,如果当前用户的用户名是"username",那么他的家目录路径将是"/home/username"。
3.4.2 SHELL 环境变量
环境变量SHELL是指当前用户所使用的默认Shell程序。在Linux和其他类Unix系统中,Shell是一种用于与操作系统交互的命令行界面程序。
当用户登录时,系统会根据用户的设置自动设置SHELL环境变量,以指定用户所使用的默认Shell程序。通常情况下,Linux系统中使用的Shell程序是Bash Shell(Bourne-Again SHell),但也有其他可用的Shell程序,如zsh、ksh等。
SHELL环境变量通常被用于在脚本和程序中引用当前用户所使用的默认Shell程序。可以通过在终端中输入"echo $SHELL"来查看当前用户所使用的默认Shell程序。
在Linux系统中,Shell程序允许用户通过命令行界面与操作系统进行交互,并执行各种命令和操作。Shell还提供了许多功能强大的特性,如命令行历史记录、命令自动补全、命令别名等,使得用户可以更加高效地使用系统。
3.4.3 HISTSIZE 环境变量
HISTSIZE 用于控制bash shell中历史命令列表的大小。该变量指定了历史命令列表中可以保存的命令数量,超过该数量的命令将会被自动删除。
默认情况下,HISTSIZE的值为500,即历史命令列表最多可以保存500条命令。如果需要设置不同的值,可以通过在终端中使用export命令进行设置,例如:
export HISTSIZE=1000
上述命令将HISTSIZE的值设置为1000,即历史命令列表最多可以保存1000条命令。
使用HISTSIZE可以方便地管理历史命令列表,并避免历史命令列表过大导致系统性能下降的情况。另外,还可以结合其他环境变量(如HISTFILESIZE)和命令(如history)来进一步管理历史命令列表。
history 10 // 查看前10条历史命令
history |wc -l //查看历史命令数量
3.5 获取环境变量
3.5.1 getenv
在Linux中,可以使用getenv函数来获取环境变量的值。该函数的定义如下:
char *getenv(const char *name);
其中name参数是要获取的环境变量的名称,该函数返回一个指向字符串的指针,该字符串表示该环境变量的值。如果未找到指定名称的环境变量,则返回NULL。
以下是一个示例程序,演示了如何使用getenv函数获取环境变量的值:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *path = getenv("PATH");
if (path == NULL) {
printf("PATH environment variable is not set.\n");
} else {
printf("PATH environment variable value is: %s\n", path);
}
return 0;
}
上述程序使用getenv函数获取名为PATH的环境变量的值,并将其打印到终端上。如果未找到该环境变量,则输出一条错误信息。
除了getenv函数,Linux还提供了其他一些函数来操作环境变量,例如setenv函数和unsetenv函数,这些函数可以用来设置和删除环境变量。
3.5.2 本地变量与环境变量
在命令行上运行的大部分的指令,它的父进程都是bash,bash创建子进程(fork),子进程执行你的命令。
下面是程序查看进程ID和父进程ID的方式:
#include<unistd.h>
printf("I am a proc: pid:%d, ppid:%d\n", getpid(), getppid());
运行后出现查看父进程的id,再查看父进程是谁
ps axj |head -1 && ps axj |grep (ppid)
在命令行中我们可以定义两种变量:本地变量和环境变量
(1)本地变量
MY_VAL=“you can see me”
使用getenv获取环境变量
int main()
{
printf(“%s\n”, getenv(“PATH”);
printf(“%s\n”, getenv(“MY_VAL”);
}
运行第一行代码没有问题,第二行会出现segmentation fault
段错误
原因MY_VAL是本地变量,只能够在当前shell命令行解释器内被访问,不可以被子进程继承。
在操作系统中,当一个进程调用fork()系统调用创建一个子进程时,子进程会继承父进程的大部分资源和状态,包括代码段、数据段、堆栈、文件描述符等。但是,子进程并不会继承父进程的本地变量。
本地变量是指在函数内部定义的变量,它们通常分配在堆栈上,而堆栈是进程内存空间中的一段区域,存储着程序执行时的临时数据。当一个函数被调用时,它会在堆栈上分配一段内存空间用于存储本地变量,当函数返回时,这些内存空间会被释放。
由于子进程是通过复制父进程的内存空间来创建的,因此子进程的堆栈和父进程的堆栈是相互独立的。当一个子进程被创建时,它会拥有自己的堆栈,但是这个堆栈中并没有父进程的本地变量,因为本地变量只存在于父进程的堆栈中。
因此,在子进程中访问父进程的本地变量是不可行的,因为子进程的堆栈中没有这些变量的存储空间。如果需要在子进程中访问父进程的数据,可以使用进程间通信机制,例如管道、共享内存、消息队列等。
解决方案:将本地变量MY_VAL导入环境变量
export MY_VAL
(2)环境变量
具有全局属性,可以被子进程继承。
set查看本地变量或环境变量
set | grep MY_VAL
环境变量和本地变量是操作系统中两种不同类型的变量,它们之间有一些区别和联系。
-
作用范围不同:本地变量只在当前进程或函数中有效,它们通常用于存储临时数据。而环境变量则是在整个操作系统中都有效,它们通常用于存储一些全局配置信息。
-
存储位置不同:本地变量通常存储在堆栈中,它们的生命周期与所在的函数或进程相同。而环境变量则存储在操作系统的环境中,它们的生命周期与操作系统的进程相同。
-
访问方式不同:本地变量只能在定义它们的函数或进程内部进行访问,其他的函数或进程无法访问它们。而环境变量可以在整个操作系统中进行访问,它们通常通过操作系统提供的API进行访问。
-
优先级不同:当本地变量和环境变量的名称相同时,本地变量的优先级高于环境变量。这意味着在访问变量时,如果当前进程或函数中定义了同名的本地变量,则优先使用本地变量的值。如果没有定义本地变量,则使用环境变量的值。
3.6 内建命令
既然本地变量不可被子进程继承,那echo也是命令,大部分命令都会作为bash的子进程运行,echo会被bash创建的子进程运行,那么怎么也能看本地变量呢?
其实echo、set、env、export等是内建命令(built-in command)————是shell程序内部的一个函数,如果某个命令不是内建命令,bash才会为其fork一个子进程运行该命令。
需要注意的是,即使echo是内置命令,也仅能访问当前进程中定义的本地变量。如果在另一个进程中定义了相同名称的本地变量,echo仍然无法访问它。
3.7 清除变量
在Linux中,unset是一个用于删除变量的命令。它可以用来删除环境变量、shell变量和用户定义的变量等。
unset命令的基本语法如下:
unset [选项] [变量名]
其中,选项可以是以下任意一个:
-
-f
:强制删除函数名称的定义。如果不使用该选项,unset命令将不会删除函数定义,因为函数定义不是变量。 -
-v
:删除变量定义。如果不使用该选项,unset命令将默认删除变量定义。
变量名是要删除的变量的名称,可以是环境变量、shell变量或用户定义的变量等。
例如,要删除名为MYVAR的变量,可以使用以下命令:
unset MYVAR
如果要强制删除名为myfunction的函数定义,可以使用以下命令:
unset -f myfunction
需要注意的是,使用unset命令删除变量或函数定义后,它们将不再存在于当前shell环境中。如果需要在其他shell环境中使用相同的变量或函数定义,需要重新定义它们。
另外,由于环境变量是在操作系统级别上定义的全局变量,删除环境变量需要使用其他命令(例如export)或编辑相关配置文件(例如~/.bashrc、/etc/profile等)。
四、命令行参数
在 Linux 命令行中,参数通常是指在命令行中输入的选项和参数。在命令行中,选项通常以一个短横线(-)或者两个短横线(–)开头,参数则是命令需要的值,用于指定命令的具体行为。以下是一些常见的 Linux 命令行参数:
- 选项参数:选项参数以一个短横线(-)或者两个短横线(–)开头。它们通常用于指定命令的具体行为,比如指定命令的输出格式或者运行方式。例如,
ls -l
命令会以长格式列出当前目录下的文件,其中-l
就是一个选项参数。 - 短选项和长选项:选项参数有时被分为短选项和长选项。短选项通常由一个字母组成,长选项则由一个或多个单词组成。例如,
ls -l
命令中的-l
是一个短选项,而ls --color=auto
命令中的--color=auto
是一个长选项。 - 参数参数:参数参数是命令需要的值,用于指定命令的具体行为。例如,
cp file1 file2
命令将文件file1
复制到文件file2
中,其中file1
和file2
就是参数参数。 - 位置参数:位置参数是指在命令行中没有被标记为选项或参数的参数。它们通常用于指定命令要操作的文件或目录。例如,
ls -l /home/user/Documents
命令中的/home/user/Documents
就是一个位置参数,表示要列出该目录下的所有文件和子目录。
4.1 main函数的参数
在C语言中,main函数的参数可以通过命令行参数传递给程序。在Linux中,可以使用argc和argv参数来访问这些命令行参数。
argc是一个整数类型的参数,它表示命令行参数的数量(包括程序名称本身)。argv是一个字符指针数组,它包含了所有的命令行参数,其中argv[0]表示程序名称本身,argv[1]表示第一个命令行参数,以此类推。
要在Linux中打印C语言的main函数参数,可以在程序中使用argc和argv参数。例如,下面是一个简单的C程序,它可以打印出所有的命令行参数:
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
printf("程序名称:%s\n", argv[0]);
for (i = 1; i < argc; i++) {
printf("参数 %d: %s\n", i, argv[i]);
}
return 0;
}
在Linux中,可以使用gcc等工具来编译该程序,例如:
gcc -o args args.c
然后,在命令行中执行该程序,并传递一些参数,例如:
./args hello world 123
该程序将会输出以下内容:
程序名称:./args
参数 1: hello
参数 2: world
参数 3: 123
argv的最后一个位置是NULL
因此,通过使用argc和argv参数,可以在Linux中打印C语言的main函数参数。
4.2 为什么存在命令行参数?
我们可以利用下述例子来更好地解释:
以下是一个基于Linux使用C利用main函数的参数实现加减法的示例程序:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 4) {
printf("Usage: %s [add/subtract] [num1] [num2]\n", argv[0]);
return 1;
}
char *operation = argv[1];
int num1 = atoi(argv[2]);
int num2 = atoi(argv[3]);
int result;
if (strcmp(operation, "add") == 0) {
result = num1 + num2;
} else if (strcmp(operation, "subtract") == 0) {
result = num1 - num2;
} else {
printf("Invalid operation: %s\n", operation);
return 1;
}
printf("%d %s %d = %d\n", num1, operation, num2, result);
return 0;
}
在该程序中,我们首先检查命令行参数的数量是否正确,如果不正确,则打印用法并返回错误码。接下来,我们将第一个参数作为操作符,并将后面两个参数解析为整数。然后,我们根据操作符进行加法或减法运算,并打印出结果。
例如,要执行加法运算,可以使用以下命令:
./program add 10 20
该程序将输出以下内容:
10 add 20 = 30
类似地,要执行减法运算,可以使用以下命令:
./program subtract 50 30
该程序将输出以下内容:
50 subtract 30 = 20
通过利用main函数的参数,我们可以轻松地实现不同的运算操作,从而让程序更加灵活和可配置。
这样就好理解命令参数了,如ls -l -a,后面的参数即为ls这个程序的参数。
可以根据不同的参数实现不同功能。
命令行参数意义:帮助我们设计出,同一个程序,不同的业务功能。
4.3 main函数的第三个参数char* env[]
指针数组,每个元素指向的是一个一个的环境变量。结构和第二参数类似。
int main(int argc, char* argv[], char* env[])
{
for(int i = 0; env[i]; i++)
{
printf("env[%d]: %s\n", i, env[i]);
}
return 0;
}
功能和env相似。
第三个参数的意义:继承环境变量,从而实现环境变量被子进程继承下去,从而拥有“全局属性”。
4.4 通过第三方变量获取环境变量
以下是一个基于Linux使用C输出环境变量参数的示例程序:
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main() {
char **env = environ;
while (*env) {
printf("%s\n", *env);
env++;
}
return 0;
}
在该程序中,我们使用environ全局变量来获取环境变量参数,并使用while循环遍历该变量。在每次循环中,我们打印出当前环境变量参数的值,并将指针向后移动到下一个环境变量参数。
例如,要打印出所有的环境变量参数,可以使用以下命令:
./program
该程序将输出所有的环境变量参数的值,如下所示:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
LANG=en_US.UTF-8
HOME=/home/user
...