To read and execute the operating system, you must have a resource (such as memory, CPU, etc.) for that.The process of executing the program that summarizes such resources is the process.The process is newly created when the operating system reads and runs an executable file.
In this series, you will look at the process of low layers of computer systems in GO language, and we will look at the process several times from this time.What we handle in this article is as follows.
Since the process is a concept at the center of the computer system, you cannot write a system on a system without ignoring its existence at all.For this reason, in the previous series, information related to the process has been made smaller.First, let's recall what kind of process has come out in the previous series.
File description and process
In the second installment of the series, we mentioned the file description as a mechanism to generalize the input / output with the outside.The kernel creates a management table for what kind of input / output is performed in each process every time a new process is created.The index value is the file disk lipter.
Input / output and process
これまでの連載では、Go言語でファイルやソケット、標準入出力、標準エラー出力といった外部との入出力を行う方法をたくさん見てきました。 ソケットを使った入出力については第6回から第9回、ファイルの入出力については第10回から第12回で触れました。io.Reader
やio.Writer
といったインタフェースは、プロセスが外部との入出力に使います。 それらのインタフェースの裏に、ファイルやソケットや標準入出力、標準エラー出力があります。
The exchange between the process and the outside world is via the system call
In the 5th series, I mentioned the system call.The process is very shy, and you can't say "gave me data" or "process this" to other processes, so all exchanges are performed via the OS via the system call.Data reading from files and sockets and the current time are all via system calls.The only thing that can be done with the power is simple numerical calculations.
The process includes a variety of information, from what you need to execute the program and what you need for input and output with external processes.
Let's access this information using Go language functions.
プロセスには必ず、プロセスごとにユニークな識別子があります。それがプロセスIDです。 Go言語では、os.Getpid()
を使って現在のプロセスのプロセスIDを取得できます。
また、ほとんどのプロセスはすでに存在している別のプロセスから作成された子プロセスとなっているので、親のプロセスIDを知りたい場合もあります。 親のプロセスIDはos.Getppid()
で取得できます。
package main import ( "fmt" "os") func main() { fmt.Printf("プロセスID: %d\n", os.Getpid()) fmt.Printf("親プロセスID: %d\n", os.Getppid())}
プロセスIDは、Windowsならタスクマネージャ(デフォルトではオフになっているので表示メニューからPIDを追加する必要があります)、macOSならアクティビティモニター、POSIX系OSであればps
コマンドで出てくるものと同じです。 なお、Google Native Client(NaCl)の場合には、プロセスIDを取得すると常に定数値が返ります。
OS | プロセスID | 親のプロセスID |
---|---|---|
API | os.Getpid() | os.Getppid() |
NaCl以外 | ◯ | ◯ |
NaCl | 定数(3)を返す | 定数(2)を返す |
I introduced that the process has a parent -child relationship.Parents and children are not the only relationship between the processes.
There is a group that bundles the process, and the process has ID information indicating the group.Friends who are connected by pipes as follows are one process group (alias job).
$cat sample.go | echo⏎
上記の例では、cat
コマンドとecho
コマンドが同じプロセスグループになります。 プロセスグループに対するIDは、Linuxの場合、グループ内に含まれるコマンドの代表のプロセスIDになっています。
There is a session group as a concept similar to a process group.If the application starts from the same terminal, it will be the same session group.The same session group is the same session group connected to the same keyboard and output to the same terminal.
To see the ID of the process group and session group in the GO language:
package main import ( "fmt" "os" "syscall") func main() { sid, _ := syscall.Getsid(os.Getpid()) fmt.Fprintf(os.Stderr, "グループID: %d セッションID: %d\n", syscall.Getpgrp(), sid)}
Windowsは、プロセスグループとセッショングループに対応する情報を持っていません。 Solarisのプロセスグループ取得はなぜかテスト用のスタブとして実装されており1、標準ライブラリには実装されていません。
プロセスグループ取得 | 指定プロセスのプロセスグループ取得 | 指定プロセスのプロセスグループ設定 | |
---|---|---|---|
API | syscall.Getpgrp() | syscall.Getpgid() | syscall.Setpgid() |
Linux/BSD系OS | ◯ | ◯ | ◯ |
Solaris | ◯ | ||
Windows/Plan9/NaCl |
指定プロセスのセッショングループ取得 | 指定プロセスのセッショングループ設定 | |
---|---|---|
API | syscall.Getsid() | syscall.Setsid() |
Linux/BSD系OS | ◯ | ◯ |
Solaris | ◯ | ◯ |
Windows/Plan9/NaCl |
The process works with someone's user authority.Also, users belong to several groups (the names are confusing, but this group is a different concept from the process group earlier).Users can only belong to one of the main groups, but in multiple sub groups.
ユーザーとグループの権限は、ファイルシステムの読み書きの権限を制限するのに使われます。 ファイルシステムの読み書きの権限には、「読み」「書き」「実行」の3種類の権限があり、それぞれの権限が「所有者」「同一グループ」「その他」の3セットあります。 3種類の権限をr
、w
、x
の文字で表すことで、権限を表現する9桁の「記号表記」と、それぞれの権限を4、2、1の数値の足し算として3桁の8進数表記があります。
記号表記 | 8進数表記 | 意味 |
---|---|---|
-rwxr-xr-x | 0755 | 所有者は全操作。それ以外のユーザーは実行を許可 |
-rw-r--r-- | 0644 | 所有者は読み書き、それ以外のユーザーは読み込みのみ許可 |
To display the user ID, group ID, and subgroup as follows:
package main import ( "fmt" "os") func main() { fmt.Printf("ユーザーID: %d\n", os.Getuid()) fmt.Printf("グループID: %d\n", os.Getgid()) groups, _ := os.Getgroups() fmt.Printf("サブグループID: %v\n", groups)}
When the child process is launched, the child process will take over the parent process user ID and group ID.
WindowsはPOSIXのグループとは多少異なる、セキュリティIDというシステムのセキュリティのデータベースのIDで権限の管理を行っています。GetTokenInformation
2というAPIで詳細情報が取得できますが、Go言語では実装されていません。
また、他のOSでも、ファイル以外の読み書きの権限管理は別の仕組みが用意されています。 macOSでは、10.5のLeopardからはApple Open Directory3という仕組みを利用していて、こちらでOSの権限管理を行っています。 GUIのシステム環境設定のユーザーやグループ、あるいはdscl
コマンドによる管理が行われます。
ユーザーIDとグループIDの設定は一部のOSでsyscall
パッケージで提供されています。Linuxは他のOSと違い「プロセスではなく、現在のスレッドにしか効果がない」という理由で1.44からエラーを返す実装になっています。Setgroups()
も同じ理由で無効な気がしますが、こちらはそのままとなっています。
ユーザーID取得 | ユーザーID設定 | グループID取得 | グループID設定 | |
---|---|---|---|---|
API | os.Getuid() | syscall.Setuid() | os.Getgid() | syscall.Setgid() |
BSD系OS/Solaris | ◯ | ◯ | ◯ | ◯ |
Linux | ◯ | エラーを返す | ◯ | エラーを返す |
Windows | 定数値(-1)を返す | 定数値(-1)を返す | ||
Plan9 | 定数値(-1)を返す | 定数値(-1)を返す | ||
NaCl | 定数値(1)を返す | 定数値(1)を返す |
補助グループID一覧取得 | 補助グループID一覧設定 | |
---|---|---|
API | os.Getgroups() | syscall.Setgroups() |
BSD系OS/Solaris | ◯ | ◯ |
Linux | ◯ | ◯ |
Windows | エラーを返す | |
Plan9 | 長さゼロの配列を返す | |
NaCl | 定数1が入った配列を返す |
プロセスのユーザーのIDやグループIDは、通常は親プロセスのものを引き継ぎます。 しかしPOSIX系OSでは、SUID
、SGID
フラグを付与することで、実行ファイルに設定された所有者(実効ユーザーID)と所有グループ(実効グループID)でプロセスが実行されるようになります5。 これらのフラグがないときは、実効ユーザーIDも実効グループIDも、元のユーザーIDとグループIDと同じです。 これらのフラグが付与されているときは、ユーザーIDとグループIDはそのままですが、実効ユーザーIDと実効グループIDが変更されます。
Effective user IDs and effective group IDs can also be obtained in GO language as follows.
package main import ( "fmt" "os") func main() { fmt.Printf("ユーザーID: %d\n", os.Getuid()) fmt.Printf("グループID: %d\n", os.Getgid()) fmt.Printf("実効ユーザーID: %d\n", os.Geteuid()) fmt.Printf("実効グループID: %d\n", os.Getegid())}
Even if you execute this code as it is, you can see that there is no particular change in the user ID, effective user ID, group ID and effective group ID.The next execution example is the result on my MacOS (depending on the other OS, the girder of the group ID may be quite small).
# 実行ファイルを作る$go build -o uid uid.go⏎ # そのまま実行してみる$./uid⏎ユーザID: 755476792グループID: 1522739515実効ユーザID: 755476792実効グループID: 1522739515
今度はSUIDを付けて実行してみましょう (macOSでは、フラグの付与とオーナーの変更にsudo
が必要です)。 実効ユーザーが変わっていることが確認できます。
# SUIDフラグをつける$sudo chmod u+s uid⏎ # オーナーを別のユーザーに変える$sudo chown test uid⏎ # 再実行してみる$./uid⏎ユーザID: 755476792グループID: 1522739515実効ユーザID: 507実効グループID: 1522739515
POSIX系OSでは、ケーパビリティ(capability)という、権限だけを付与する仕組みが提案されました。 それまで、ルート権限が必要な情報の設定・取得を行うツールでは、SUIDを付けてルートユーザーの所有にしたプログラムを用意し、ユーザー権限からも利用可能にする、といったことが行われてきました。 しかし、これでは与えられる権限が大きすぎるため、ツールにセキュリティホールがあって任意のプログラムの実行ができると、ルート権限を得るための踏み台として悪用されて被害を拡大してしまいます。 ケーパビリティは、スーパーユーザーのみが利用できた権限を細かく分け、必要なツールに必要なだけの権限を与える仕組みであり、そうしたリスクを減らします。 Linuxでは2.4から、FreeBSDでは9.0からケーパビリティが導入されました6。
Effective user IDs and effective group IDs are convenient if they are limited to the control of resources access on the file system, and other applications will decrease in the future.
実効ユーザーID取得 | 実効ユーザーID設定 | 実効グループID取得 | 実効グループID設定 | |
---|---|---|---|---|
API | os.Geteuid() | syscall.Seteuid() | os.Getegid() | syscall.Setegid() |
BSD系/Solaris | ◯ | ◯ | ◯ | ◯ |
Linux | ◯ | ◯ | ||
Windows/Plan9 | 定数値(-1)を返す | 定数値(-1)を返す | ||
NaCl | 定数値(1)を返す | 定数値(1)を返す |
現在の作業フォルダもプロセスにおける大事な実行環境のひとつです。 作業フォルダは、次のようにos.Getwd()
関数を使って取得できます。
package main import ( "fmt" "os") func main() { wd, _ := os.Getwd() fmt.Println(wd)}
作業フォルダ取得 | |
---|---|
API | os.Getwd() |
全OS | ◯ |
ファイルディスクリプタは、連載の第2回で紹介したように、ファイルやソケットなどを抽象化した仕組みです。 どのリソースも「ファイル」として扱えます。 プロセスは、これらのリソースをファイルディスクリプタと呼ばれる識別子で識別します。 カーネルはプロセスごとに、プロセスが関与しているファイル情報のリストを持っています (Linuxの場合、各要素はfile
構造体です)。 ファイルディスクリプタはこのリストのインデックス値です。
When the OS launches the process, three files have already been opened.Each is a file that supports standard input, standard output, and standard error output.
Go言語には、すでにオープン済みのファイルディスクリプタの数値をio.ReadWriter
インタフェースでラップする、os.NewFile()
という関数があります。 Go言語での標準入出力の初期化は下記のようになっています。
Stdin= os.NewFile(0, "/dev/stdin")Stdout = os.NewFile(1, "/dev/stdout")Stderr = os.NewFile(2, "/dev/stderr")
When you launch the child process, you can put data into the standard input of other processes, or read the standard output and standard error output that other processes output.This method will be introduced in the next article.
The process has input, the program processes it and the final output.In that sense, the process can be said to be like "function" or "subroutine" in GO language and other languages.
All processes have the following three input / output data:
Depending on the program, you can read and / / out of the standard input / output while running, or can also communicate with socket, but these three data are always included in the process.
コマンドライン引数は、プログラムに設定を与える一般的な手法として使われています。 Go言語では、os.Args
引数の文字列の配列として、コマンドライン引数が格納されています。
この配列をプログラムで直接利用してもいいのですが、通常は「オプションパーサー」と呼ばれる種類のライブラリを利用してパースします。 コマンドライン引数には、-o ファイル名
のような組み合わせのオプションがあったり、-o=ファイル名
と--output ファイル名
のような等価な表現があったり、自分で実装するとなると面倒なルールがたくさんあります。 オプションパーサーはそのような複雑なルールの解釈とバリデーションを引き受けてくれるライブラリです。
オプションパーサーとして代表的なライブラリは標準のflag
パッケージです。 これ以外にも、多くのパッケージがあります7。
Environment variable is an array that includes the unique settings for each user.Includes a variety of information, such as user name, home directory, current user information such as language settings, passes of executable files, and program settings.The API shown in the table below can be used in the full environment.
os.Environ() | 文字列のリストで全取得 |
---|---|
os.ExpandEnv() | 環境変数が埋め込まれた文字列を展開 |
os.Setenv() | キーに対する値を設定 |
os.LookupEnv() | キーに対する値を取得(有無をboolで返す) |
os.Getenv() | キーに対する値を取得 |
os.Unsetenv() | 指定されたキーを削除する |
os.Clearenv() | 全部クリアする |
Web applications and environment variables are inseparable.In the old CGI, client request header information, GET method queries, and server information were handed to the program as environment variables.Even recently, we use environment variables to switch mode between the production environment and the development environment.In addition, web services using containers pass the server specific information and credentials with environment variables.
環境変数は、キー=値
という形式の文字列の配列です。 Go言語内部では、このままの形式配列と、これをマップ型にマッピングしたものを両方持っています。
少し特殊で他の言語であまり見かけない機能として、os.ExpandEnv()
があります。 これは、環境変数をそのまま使うのではなく、GOBIN=${HOME}/bin
のようにして他の環境変数を組み合わせた文字列が欲しい場合に使います。
package main import ( "fmt" "os") func main() { fmt.Println(os.ExpandEnv("${HOME}/gobin"))}
プロセス終了時にはos.Exit()
関数を呼びます。この関数は引数として数値を取り、この数値がプログラムの返り値として親プロセスに返されます。 この数値が終了コードです。
package main import ( "os") func main() { os.Exit(1)}
終了コードは非負の整数です。 一般的な慣習として、0が正常終了、1以上がエラー終了ということになっています。 安心して使える数値の上限については諸説ありますが、Windowsではおそらく32ビットの数値の範囲で使えます。 POSIX系OSでは、子プロセスの終了を待つシステムコールが5種類あります(wait
、waitpid
、waitid
、wait3
、wait4
)が、 このうちwaitid
を使えば32ビットの範囲で扱えるはずです。 それ以外の関数は、シグナル受信状態とセットで同じ数値の中にまとめられて返され、そのときに8ビットの範囲にまとめられてしまうため、255までしか使えません。
As long as I tried shells and Python as parent processes, I couldn't handle 256 or more, so it would be safe to keep it up to 255 considering the portability.
なお、wait3
とwait4
はBSD系OS由来の関数で、子プロセスのメトリックも返す高機能関数になっています。 ただし、Linuxのmanによると将来削除される予定になっていますし、この関数はPOSIXの規格外の関数です。 Go言語はwait4
を使っています。
In a tool like a task manager, the application name is displayed along with the process ID.However, there is no way to know who is a process ID.
LinuxやBSD系OSの場合、/proc
ディレクトリの情報が取得できます。 このディレクトリは、カーネル内部の情報をファイルシステムとして表示したものです。 GNU系のps
コマンドは、このディレクトリをパースして情報を得ています。 以下に示すように、/proc/プロセスID/cmdline
というテキストファイルの中にコマンドの引数が格納されているように見えます。
$cat /proc/2862/cmdline⏎bash
macOSの場合は、オープンソースになっているdarwin用のps
コマンド8の中でsysctl
システムコールを使っています。 このシステムコールはLinuxにも存在していますが、カーネルの中の情報を取り出すシステムコールという性格上、OSごとに互換性はありません。
Windowsの場合はGetModuleBaseName()
を使います9。
このあたりはプロセスモニターのようなツールを実装するときには便利ですが、現時点で多くの機能がまとまっていてクロスプラットフォームで使えるのが、@r_rudi氏作のgopsutil10です。 このパッケージを使ったサンプルを以下に紹介します。
package main import ( "fmt" "github.com/shirou/gopsutil/process" "os") func main() { p, _ := process.NewProcess(int32(os.Getppid())) name, _ := p.Name() cmd, _ := p.Cmdline() fmt.Printf("parent pid: %d name: '%s' cmd: '%s'\n", p.Pid, name, cmd)}
The sample above shows the executable file name used in the execution of the process and the argument information of the process at the time of execution.In addition, you can get a lot of information, such as host OS information, CPU information, process information, storage information.
Compared to the world seen from the process, the world seen from the OS is a bit more complicated.
The process from the OS is a "task" that runs according to the program prepared in advance by consuming CPU time.The OS job is to work efficiently for many processes.
Linuxではプロセスごとにtask_struct
型のプロセスディスクリプタと呼ばれる構造体を持っています。 プロセスを構成するすべての要素は、この構造体に含まれています。 基本的にはプロセスから見た各種属性と同じ内容ですが、それには含まれていない要素もいくつかあります。
In the 5th series, we mentioned that modern OS processes are concentrated only on their own work and cannot interfere with other processes.The process is like a fish in the aquarium.You can swim freely in your own aquarium.The only information that knows the process is fish information, but the OS side also includes the definition of this aquarium.
For example, the process has been described as having a "current folder" as a context for file systems, but you can set the root directory for each process.There is also a memory block information that defines where the memory area is.In the process information of the OS where the stack area is located, the data that the program is static and the data to be dynamically secured is laid out.
File descriptions also look like a single -dimensional array (index value) from the process perspective, but files may be shared between processes.The OS has a list of files to be masters, and the reference count is counted in the reference count.
今回は、プロセス編の第一弾として、プロセスが持つ情報を取得するGo言語の機能紹介を中心に解説しました。 紹介した機能の一部はsyscall
以下にしかなかったり、OSによっては関数は存在するけれど正しく実装されていないものもあります。 いざとなればsyscall
パッケージを使ってシステムコールを呼び出したり、cgoを使ってOSの機能を使うことはできますが、 システムコール周りのカバレッジは限定的で、Windowsも含めた多くの環境の最大公約数の機能しか提供されていません。
GO language is a job -runner that runs within the same right limit, such as Make, or execution of a server with a large imprisonment.However, GO language is not versatile, and there is not enough functions for infrastructure management of infrastructure that manages tasks while setting the user authority in detail.
The missing functions in Go languages are integrated in either or both:
A high -performance library such as GOPSUTIL may be covered by weaknesses, but first of all, it is best to gradually expand your reach while enjoying the benefits of GO language.
Next time, as the second process of the process, we will take up the external process execution.
Tweet
To the category top
ASCII Club
■ Saturday, July 6, 2019 13: 00-19: 00
3-1 Goban-cho, Chiyoda-ku, Tokyo
The seminar held on March 23 this year, "Introduction to quantum programming on quantum computers," was held repeatedly.Unlike the previous "Introduction to quantum computers learned with paper and pencils", the goal is to learn the basics of "quantum programming" necessary to calculate with quantum computers.With the cooperation of IBM, you will learn how to use a quantum circuit by combining basic gates and how to use many simulators in IBM QISKIT.
Kadokawa ASCII Research Institute Inquiry Form
Programming + Editorial Department has received a wide range of requests and consultations related to programming and education, such as planning and implementation of corporate seminars and in -business training, creating reviews of products and publications, and requesting event coverage.If you are interested in individuals, corporations, or organizations, please feel free to contact us from the above form.
Display format: PC ⁄ Smartphone