Skip to content

Commit

Permalink
进程属性和控制完成
Browse files Browse the repository at this point in the history
  • Loading branch information
polaris1119 committed Jun 25, 2016
1 parent 2e16cf3 commit 999f0cb
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 1 deletion.
8 changes: 8 additions & 0 deletions chapter10/10.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,14 @@ func UsePipe(name string, arg ...string) ([]byte, error) {

完整代码见 [os_exec](/code/src/chapter10/os_exec.go)

## 进程终止

`os.Exit()` 函数会终止当前进程,对应的系统调用不是 `_exit`,而是 `exit_group`

`func Exit(code int)`

`Exit` 让当前进程以给出的状态码 `code` 退出。一般来说,状态码 0 表示成功,非 0 表示出错。进程会立刻终止,defer 的函数不会被执行。

# 导航 #

- [第十章](/chapter10/10.0.md)
Expand Down
191 changes: 190 additions & 1 deletion chapter10/10.2.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,198 @@
# 10.2 进程属性和控制 #

每个进程都有一些属性,`os` 包提供了一些函数可以获取进程属性。

## 进程凭证

Unix 中进程都有一套数字表示的用户 ID(UID) 和组 ID(GID),有时也将这些 ID 称之为进程凭证。Windows 下总是 -1。

### 实际用户 ID 和实际组 ID

实际用户 ID(real user ID)和实际组 ID(real group ID)确定了进程所属的用户和组。登录 shell 从 `/etc/passwd` 文件读取用户 ID 和组 ID。当创建新进程时(如 shell 执行程序),将从其父进程中继承这些 ID。

可通过 `os.Getuid()``os.Getgid()` 获取当前进程的实际用户 ID 和实际组 ID;

### 有效用户 ID 和有效组 ID

大多数 Unix 实现中,当进程尝试执行各种操作(即系统调用)时,将结合有效用户 ID、有效组 ID,连同辅助组 ID 一起来确定授予进程的权限。内核还会使用有效用户 ID 来决定一个进程是否能向另一个进程发送信号。

有效用户 ID 为 0(root 的用户 ID)的进程拥有超级用户的所有权限。这样的进程又称为特权级进程(privileged process)。某些系统调用只能由特权级进程执行。

可通过 `os.Geteuid()``os.Getegid()` 获取当前进程的有效用户 ID(effective user ID)和有效组 ID(effectvie group ID)。

通常,有效用户 ID 及组 ID 与其相应的实际 ID 相等,但有两种方法能够致使二者不同。一是使用相关系统调用;二是执行 set-user-ID 和 set-group-ID 程序。

### Set-User-ID 和 Set-Group-ID 程序

`set-user-ID` 程序会将进程的有效用户 ID 置为可执行文件的用户ID(属主),从而获得常规情况下并不具有的权限。`set-group-ID` 程序对进程有效组 ID 实现类似任务。(有时也将这程序简称为 set-UID 程序和 set-GID 程序。)

与其他文件一样,可执行文件的用户 ID 和组 ID 决定了该文件的所有权。在 [6.1 os — 平台无关的操作系统功能实现](chapter06/06.1.md) 中提到过,文件还拥有两个特别的权限位 set-user-ID 位和 set-group-ID 位,可以使用 `os.Chmod` 修改这些权限位(非特权用户进程只能修改其自身文件,而特权用户进程能修改任何文件)。

文件设置了 set-user-ID 位后,`ls -l` 显示文件后,会在属主用户执行权限字段上看到字母 s(有执行权限时) 或 S(无执行权限时);相应的 set-group-ID 则是在组用户执行位上看到 s 或 S。

当运行 set-user-ID 程序时,内核会将进程的有效用户 ID 设置为可执行文件的用户 ID。set-group-ID 程序对进程有效组 ID 的操作与之类似。通过这种方法修改进程的有效用户 ID 或组 ID,能够使进程(换言之,执行该程序的用户)获得常规情况下所不具有的权限。例如,如果一个可执行文件的属主为 root,且为此程序设置了 set-user-ID 权限位,那么当运行该程序时,进程会取得超级用户权限。

也可以利用程序的 set-user-ID 和 set-group-ID 机制,将进程的有效 ID 修改为 root 之外的其他用户。例如,为提供一个受保护文件的访问,可采用如下方案:创建一个具有对该文件访问权限的专有用户(组)ID,然后再创建一个 set-user-ID(set-group-ID)程序,将进程有效用户(组)ID 变更为这个专用 ID。这样,无需拥有超级用户的所有权限,程序就能访问该文件。

Linux 系统中经常使用的 set-user-ID 程序,如 passwd。

#### 测试 set-user-ID 程序

在 Linux 的某个目录下,用 root 账号创建一个文件:

`echo "This is my shadow, studygolang." > my_shadow.txt`

然后将所有权限都去掉:`chmod 0 my_shadow.txt`。 ls -l 结果类似如下:

`---------- 1 root root 32 6月 24 17:31 my_shadow.txt`

这时,如果非 root 用户是无法查看文件内容的。

接着,用 root 账号创建一个 `main.go` 文件,内容如下:

```
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
)
func main() {
file, err := os.Open("my_shadow.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
log.Fatal(err)
}
fmt.Printf("my_shadow:%s\n", data)
}
```
就是简单地读取 `my_shadow` 文件内容。`go build main.go` 后,生成的 `main` 可执行文件,权限是:`-rwxrwxr-x`

这时,切换到非 root 用户,执行 `./main`,会输出:

`open my_shadow.txt: permission denied`

因为这时的 `main` 程序生成的进程有效用户 ID 是当前用户的(非 root)。

接着,给 `main` 设置 `set-user-ID` 位:`chmod u+s main`,权限变为 `-rwsrwxr-x`,非 root 下再次执行 `./main`,输出:

`my_shadow:This is my shadow, studygolang.`

因为设置了 `set-user-ID` 位,这时 `main` 程序生成的进程有效用户是 `main` 文件的属主,即 root 的 ID,因此有权限读 `my_shadow.txt`

### 修改进程的凭证

`os` 包没有提供相应的功能修改进程的凭证,在 `syscall` 包对这些系统调用进行了封装。因为 [https://golang.org/s/go1.4-syscall](https://golang.org/s/go1.4-syscall),用户程序不建议直接使用该包,应该使用 `golang.org/x/sys` 包代替。

该包提供了修改进程各种 ID 的系统调用封装,这里不一一介绍。

此外,`os` 还提供了获取辅助组 ID 的函数:`os.Getgroups()`

## 进程的当前工作目录

一个进程的当前工作目录(current working directory)定义了该进程解析相对路径名的起点。新进程的当前工作目录继承自其父进程。

`func Getwd() (dir string, err error)`

`Getwd` 返回一个对应当前工作目录的根路径。如果当前目录可以经过多条路径抵达(比如符号链接),`Getwd` 会返回其中一个。对应系统调用:`getcwd`

`func Chdir(dir string) error`

相应的,`Chdir` 将当前工作目录修改为 `dir` 指定的目录。如果出错,会返回 `*PathError` 类型的错误。对应系统调用 `chdir`

另外,`os.File` 有一个方法:`Chdir`,对应系统调用 `fchidr`(以文件描述符为参数),也可以改变当前工作目录。

## 改变进程的根目录

每个进程都有一个根目录,该目录是解释绝对路径(即那些以/开始的目录)时的起点。默认情况下,这是文件系统的真是根目录。新进程从其父进程处继承根目录。有时可能需要改变一个进程的根目录(比如 ftp 服务就是一个典型的例子)。系统调用 `chroot` 能改变一个进程的根目录,Go 中对应的封装在 `syscall.Chroot`

除此之外,在 `fork` 子进程时,可以通过给 `syscall.SysProcAttr` 结构的 `Chroot` 字段指定一个路径,来初始化子进程的根目录。

## 进程环境列表

每个进程都有与其相关的称之为环境列表(environment list)的字符串数组,或简称环境(environment)。其中每个字符串都以 名称=值(name=value)形式定义。因此,环境是“名称-值”的成对集合,可存储任何信息。常将列表中的名称称为环境变量(environment variables)。

新进程在创建之时,会继承其父进程的环境副本。这是一种原始的进程间通信方式,却颇为常用。环境(environment)提供了将信息和父进程传递给子进程的方法。创建后,父子进程的环境相互独立,互不影响。

环境变量的常见用途之一是在 shell 中,通过在自身环境中放置变量值,shell 就可确保把这些值传递给其所创建的进程,并以此来执行用户命令。

在程序中,可以通过 `os.Environ` 获取环境列表:

`func Environ() []string`

返回的 `[]string` 中每个元素是 `key=value` 的形式。

`func Getenv(key string) string`

`Getenv` 检索并返回名为 `key` 的环境变量的值。如果不存在该环境变量会返回空字符串。有时候,可能环境变量存在,只是值刚好是空。为了区分这种情况,提供了另外一个函数 `LookupEnv()`

`func LookupEnv(key string) (string, bool)`

如果变量名存在,第二个参数返回 `true`,否则返回 `false`

`func Setenv(key, value string) error`

`Setenv` 设置名为 `key` 的环境变量,值为 `value`。如果出错会返回该错误。(如果值之前存在,会覆盖)

`func Unsetenv(key string) error`

`Unsetenv` 删除名为 `key` 的环境变量。

`func Clearenv()`

`Clearenv` 删除所有环境变量。

```
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("The num of environ:", len(os.Environ()))
gctrace, ok := os.LookupEnv("gctrace")
if ok {
fmt.Println("gctrace==", gctrace)
} else {
fmt.Println("gctrace not exists!")
os.Setenv("gctrace", "2")
fmt.Println("after setenv:", os.Getenv("gctrace"))
}
os.Clearenv()
fmt.Println("clearenv, the num:", len(os.Environ()))
}
// Output:
// The num of environ: 25
// gctrace not exists!
// after setenv: 2
// clearenv, the num: 0
```

另外,`ExpandEnv``Getenv` 功能类似,不过,前者使用变量方式,如:

os.ExpandEnv("$gctrace") 和 os.Getenv("gctrace") 是一样的。

实际上,`os.ExpandEnv` 调用的是 `os.Expand(s, os.Getenv)`

`func Expand(s string, mapping func(string) string) string`

`Expand` 能够将 ${var} 或 $var 形式的变量,经过 mapping 处理,得到结果。

# 导航 #

- 上一节:[创建进程](10.1.md)
- 下一节:[xxx](10.3.md)
- 下一节:[进程间通信](10.3.md)

1 change: 1 addition & 0 deletions preface.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* 第十章 [进程、线程与 goroutine](chapter10/10.0.md)
- 10.1 [创建进程](chapter10/10.1.md)
- 10.2 [进程属性和控制](chapter10/10.2.md)
- 10.3 [进程间通信](chapter10/10.3.md)
* 第十一章 网络通信与互联网 (Internet)
* 第十二章 email
* 第十三章 应用构建、debug 与测试
Expand Down

0 comments on commit 999f0cb

Please sign in to comment.