一、基本概念

用户:  

  对于多用户多任务的Linux操作系统来说,是通过什么来区分不同的使用者呢?   答案:  用户。

    用户:用户就是获取资源或服务的凭证。当我们登录系统的时候,系统会提示输入用户名和密码进行登录验证。对于Linux操作系统来说,用户通常表现为一个唯一的数字标识,以 UID 来表示和识别。


权限

  定义了用户对计算机资源(CPU,内存,文件等等)的访问能力。当用户经过 authentication(认证机制)登录系统之后,还需要 authorization 对用户进行授权。


进程

  什么是进程? 程序的执行过程。所谓对计算机的操作,比如双击某个应用程序图标或者是通过命令提示符敲入命令,其最终产生的结果就是【创建进程】。事实上,看似用户的操作,实际上是进程在代替我们完成具体操作,进程就是我们访问计算机资源的代理, 所谓我们执行的操作,实际上是 带有我们身份的进程 执行的操作

  比如:打开word,就是创建一个WINWORD.EXE进程。 打开chrome浏览器,就创建一个chrome.exe进程。

wKioL1UMUXmRzH0qAAHuG8qxm5M781.jpg

 

执行 tail -f 命令,就是创建一个tail进程。

wKiom1UMUEzhTr1OAAC2odijv-I529.jpg


进程权限

    既然进程需要代替我们去完成某些动作,当我们需要访问资源的时候,必须给相应的进程赋予权限。【也就是说进程必须要携带发起这个进程用户的身份信息】,才能够进行合法操作。

【进程也是有属主和属组的】。

  当Linux完成开机启动之后,init进程会执行一个login的子进程。我们将用户名和密码传递给login子进程。login在查询了/etc/passwd/etc/shadow,并确定了其合法性之后,运行(利用exec)一个shell进程,shell进程有效身份被设置成为该用户的身份。由于此后fork此shell进程的子进程都会继承有效身份。


    下面来说说什么是 effective user ID,  effective group ID?


引用《Advanced Programming in the UNIX Environment - Second Edition

wKiom1UMTuWiiki8AATrNMCD_Wc568.jpg

wKiom1UMUN-i8-mXAAFTucYgkck056.jpg

wKioL1UMUgzhiWDWAATi53GgEus228.jpg


    本人对Unix下的进程的实际用户ID和有效用户ID一直都比较迷惑,没有完全搞清楚。最近温习APUE(《高级UNIX环境编程》),终于对这两个概念有了一个清晰的认识,看来经典著作绝对需要温习多遍,才能领略其中的奥秘。

    在Unix进程中涉及多个用户ID和用户组ID,包括如下:

1、实际用户ID和实际用户组ID:标识我是谁(据说这是一个变态的哲学问题,难死一片哲学家)。也就是记录在/etc/passwd中的或者是登录用户的uid和gid,比如我的Linux以simon登录,在Linux运行的所有的命令的实际用户ID都是simon的uid,实际用户组ID都是simon的gid(可以用id命令查看)。 
2、有效用户ID和有效用户组ID:进程通过它用来决定我们对资源的访问权限。一般情况下,有效用户ID等于实际用户ID,有效用户组ID等于实际用户组ID。当设置-用户-ID(SUID)位设置,则进程有效用户ID等于文件的所有者的uid,而不是实际用户ID;同样,如果设置了设置-用户组-ID(SGID)位,则进程有效用户组ID等于文件所有者的gid,而不是实际用户组ID。


    Unix系统通过进程的有效用户ID和有效用户组ID来决定进程对系统资源的访问权限。


二、Linux创建进程的机制

wKiom1UMU5CzuSywAAIFgbsUbF4784.jpg

  实际上,当计算机开机的时候,内核(kernel)只建立了一个init进程。Linux kernel并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。fork是一个系统调用。进程存活于内存中。每个进程都在内存中分配有属于自己的一片空间 (address space)。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。

  老进程成为新进程的父进程(parent process),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。所以说,所有的进程也构成一个以init为根的树状结构。


第一步:fork():复制进程映像

第二步:exec 系列函数:替换进程映像

wKiom1UMUryg18juAACLjPqvVVo521.jpg

三、外部命令执行机制

外部命令就是由Shell副本(新的进程)所执行的命令,基本的过程如下:

a. shell通过fork()建立一个新的进程。此进程即为当前Shell的一个副本。
b. 在新的进程里,在
PATH变量内所列出的目录中,寻找特定的命令。
  当命令名称包含有斜杠(/)符号时,将略过路径查找步骤。
c. 在新的进程里,通过 
exec 系列函数,以所找到的新程序取代执行中的Shell进程并执行。
d. 子进程退出后,最初的Shell会接着从终端读取下一条命令,和执行脚本里的下一条命令。


具体为什么命令执行需要指定路径??

涉及到系统底层调用 exec, Unix like给我们提供的system call 就是需要给定文件路径。


  假设我们在shell中执行 cat a.txt 命令,其过程可以描述如下:

wKiom1UMV1Px4ZpzAAEbSQQurRU807.jpg

结合上述的访问权限,我们来分析一下整个过程。

使用ls -l 命令查看文件的权限。

[skypegnu@skype ~]$ id
uid=500(skypegnu) gid=500(skypegnu) groups=500(skypegnu) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

[skypegnu@skype ~]$ ls -l /bin/cat
-rwxr-xr-x. 1 root root 45224 Nov 22  2013 /bin/cat
[skypegnu@skype ~]$ ls -l a.txt
-rw-rw-r--. 1 skypegnu skypegnu 7259 Mar 21 01:31 a.txt

我们可以看出,skypegnu用户real UID, real GID 为500。

wKioL1UMXEyAfWOiAABWPfY1vBI408.jpg

  当我们执行一个进程的时候,进程的Effective user ID 等于 real user ID进程的Effective group ID 等于 real group ID。 

  当我们以skypegnu用户登录系统,在bash键入 cat a.txt 命令之后,那么bash会根据 PATH变量指定的路径进行查找,在磁盘上找到 /bin/cat 文件之后,bash进程会尝试载入/bin/cat到内存,由于/bin/cat的属主、属组都是root,对于cat文件本身来说,skypegnu就是其他人的权限 r-x,可以看到是可以执行的。那么此时,/bin/cat被载入内存,成为进程。由于是skypegnu发起的,所以cat进程的有效身份:属主/属组分别是skypegnu:skypegnu。

  同样,cat进程去访问a.txt这个文件的时候,cat进程的EUID 等于 a.txt文件owner的ID,cat进程的有效UID属于a.txt文件的属主,所以cat进程拥有对此文件的 rw- 权限,那么自然就可以读写 a.txt 的文件内容了。

cat进程         skypegnu  skypegnu

a.txt  -rw-rw-r--.  skypegnu  skypegnu

4、如何执行shell脚本

路径与命令搜寻顺序:

  • 以相对/绝对路径运行的命令

  • 由alias找到该命令执行

  • 由bash内建(builtin)的命令执行

  • 通过 PATH 变量顺序找到的第一个命令执行

内置命令: shell 内置, 内建的,相当于shell的子函数

外部命令:在文件系统的某个路径下的一个可执行文件


执行外部命令

    外部命令执行时,会创建当前shell的子进程。根据fork机制,该子shell会继承父进程的数据结构,包括环境变量等。系统自动执行的脚本(非命令行启动),由于没有相应的环境变量,就需要自我定义需要的各环境变量才行。 

    用户在命令行输入命令后,一般情况下Shellfork一个子进程并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cdaliasumaskexit等命令即是内建命令。

# man builtin

# type cd       # 命令类型

有很多内建命令,如exportshiftifeval[forwhile等等。内建命令虽然不创建新的进程,但也会有Exit Status,通常也用0表示成功非零表示失败,虽然内建命令不创建新的进程,但执行结束后也会有一个状态码,也可以用特殊变量$?读出。


执行shell脚本

    Shell会fork一个子进程并调用exec执行./script.sh这个程序,exec系统调用应该把子进程的代码段替换成./script.sh程序的代码段,并从它的_start开始执行。然而script.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用(#!)Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序

$ /bin/sh ./script.sh

bashfork/exec一个子Shellsh)用于执行脚本,子shell继承父进程的环境变量,父进程bash等待子进程sh终止。

如何在shell脚本中执行cd命令??这是一个很典型的错误

cd Shell的内置命令,像if,while一样。因为shell执行命令时,总是fork新开启一个shell,子shell能够继承父进程的环境变量,但是子shell确不能改变父shell的环境变量。然后再调用exec执行cd这个命令,父进程wait,执行完成后,父进程不受一点影响。这就是把cd作为内置命令的原因。