よく忘れるのでメモ。随時アップデート中。
I/O編
open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open()
は指定したパスのファイルをオープンし、そのファイルディスクリプタを返します。
戻り値
オープンに成功した場合はそのファイルディスクリプタを返し、エラーが発生した場合は-1
を返します。
その場合、errno
に適切な値が設定されます。
close
#include <unistd.h>
int close(int fd);
戻り値
成功した場合は0
を、エラーの場合は-1
を返します。
エラーの場合はerrno
に適切な値が設定されます。
creat
#include ...
int creat(const char* pathname, mode_t mode);
write
ファイル(ソケット)へのデータ書き込み
#include <unistd.h>
int write(int handle, void *buf, unsigned n);
パラメータ | 説明 |
---|---|
int handle | ファイルハンドル |
void *buf | データアドレス(ポインタ) |
unsigned n | データの大きさ |
戻り値は書き込みに成功した場合は書き込んだバイト数を、失敗した時は-1
を返します。
read
ファイル(ソケット)からのデータ読み込み
#include <unistd.h>
int read(int handle, void *buf, unsigned n);
パラメータ | 説明 |
---|---|
int handle | ファイルハンドル |
void *buf | データを格納するアドレス(ポインタ) |
unsigned n | データの大きさ |
戻り値は読み込みに成功した場合は読み込んだバイト数を、失敗した時は-1
を返します。
sscanf
#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
str
から、書式(format
にしたがってscanf
関数と同様の変換を続く変数のアドレスに格納する。
example
char *text = "Name: edo Age: 20";
char output1[256];
int output2;
char *format = "Name: %s Age: %d";
sscanf(text, format, output1, &output2);
printf("名前: %s、歳: %d\n", output1, output2); // => "名前: edo、歳: 20"
sprintf
書式format
にしたがって、printf()
関数と同様の変換を行った出力を、変数str
に格納します。
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
戻り値
成功時:str
に格納した文字数(最後の`\0'は除く)
失敗時:EOF
select
select()
システムコールは同期的多重I/Oの仕組みを提供します。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
select()
システムコールは渡されたファイルディスクリプタセットのいずれかがI/O可能になるまで、または指定された時間が経過するまで内部でブロックすします。
タイムアウトに利用されるtimeval
構造体の宣言は以下のようになっています。
#include <sys/time.h>
struct timeval {
long tv_sec; // seconds
long tv_usec; // microseconds
};
select()
に渡すファイルディスクリプタの種類は3種類あり、readfds
には読み取り用の、writefds
には書き込み用の、そしてexceptfds
には例外または帯域外データ(緊急データ、急送データ(out-of-band data))それぞれのイベント発生を待ちます。
渡したファイルディスクリプタのどれかがI/O可能になると、I/O可能になったファイルディスクリプタだけを残すように、渡されたファイルディスクリプタセットを変更します。
つまり、残されたファイルディスクリプタを調べればどのファイルディスクリプタがI/O可能になったかが分かる、というわけです。
ちなみに、最初の引数のn
には、渡すファイルディスクリプタの最大値+1の値を渡します。
(内部的にはその値までの間のファイルディスクリプタを捜査するということ?)
ファイルディスクリプタの操作
select()
システムコールに渡すファイルディスクリプタセットは直接には操作しません。
操作用のヘルパーマクロが用意されているのでそれを利用します。
fd_set writefds;
// ファイルディスクリプタセットからすべてのファイルディスクリプタを削除します。
FD_ZERO(&writefds);
// 第一引数のファイルディスクリプタをセットに追加します。
FD_SET(fd, &writefds);
// 第一引数のファイルディスクリプタをセットから削除します。
FD_CLR(fd, &writefds);
// 第一引数のファイルディスクリプタがセット内にあるかを調べます。
// セットされている場合は非0を、されていない場合は0を返します。
FD_ISSET(fd, &writefds);
mmap
mmap()
システムコールは、ファイルディスクリプタfd
に対応するファイルをメモリにマッピングします。
offset
バイト目からlen
バイト数分の領域をマッピングします。
これをする大きな利点は、ユーザー空間とカーネル空間でのバッファのコピーなどのオーバーヘッドを省略し、メモリアクセスと同じ方法でファイルへ読み書きできるようになる点です。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
munmap
munmap()
システムコールは、mmap()
システムコールでマッピングしたメモリ領域を解放します。
#include <sys/mman.h>
int munmap(void *addr, size_t len);
munmap()
システムコールは、プロセスアドレス空間のaddr
からlen
バイト数分のページを削除します。
stat
stat()
システムコールはファイルのメタ情報を取得します。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
stat()
は普通のファイルのパスを、fstat()
はファイルディスクリプタを渡します。
*buf
には指定したファイルのメタ情報が格納されます。
lstat()
はシンボリックリンクの場合に動作が異なり、参照先のファイルではなくシンボリックリンク自身の情報を返します。
3つのシステムコールはどれも、処理が成功すると0
を返し、ファイルのメタ情報をstat構造体に格納します。
エラーが発生した場合は-1
を返します。
stat構造体
stat構造体は以下のように宣言されています。
#include <sys/stat.h>
struct stat {
dev_t st_dev;
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
off_t st_size;
blksize_t st_blksize;
blkcnt_t st_blocks;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
};
getenv
ホスト環境が提供する環境リストから、name
にマッチする文字列を検索します。
#include <stdlib.h>
char *getenv(const char *name);
プロセス編
system
#include <stdlib.h>
int system(const char *string);
system
関数はコマンドプロセッサへstring
の文字列を渡し、実行します。
sample
ls
コマンドを実行して/tmp
ファイル経由で取得する。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
char filename[80];
char str[512];
char *ptr;
FILE *fp;
sprintf(filename, "/tmp/ls%d.tmp", getpid());
sprintf(str, "ls -1 > %s", filename);
system(str);
if ((fp = fopen(filename, "r")) == NULL) {
fprintf(stderr, "Error!\n");
return 1;
}
while(1) {
fgets(str, 512, fp);
if (feof(fp)) {
break;
}
ptr = strchr(str, '\n');
if (ptr != NULL) {
*ptr = '\0';
}
printf("%s\n", str);
}
fclose(fp);
sprintf(str, "rm -f %s", filename);
system(str);
return 0;
}
pipe
pipe()
はパイプを実行する関数です。
#include <unistd.h>
int pipe(int pipefd[2]);
引数にはint
型の配列を渡します。
ここに、パイプの書き込み用と読み込み用のファイルディスクリプタが渡されます。
(ちなみに添字0
が読み込み用、添字1
が書き込み用)
sample
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
int pipefd[2];
if (pipe(pipefd) < 0) {
perror("pipe");
return 1;
}
char *s = "test";
write(pipefd[1], s, strlen(s));
char buf[128];
read(pipefd[0], buf, sizeof(buf));
printf("buf=[%s]\n", buf);
close(pipefd[1]);
close(pipefd[0]);
return 0;
}
exec
バイナリをメモリにロードし、実行します。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
第一引数に実行するバイナリのパス、第二引数以降は可変引数となり、実行するプログラムに渡す引数となります。
ただし、Unixの規約に従い、プログラムに渡す第一引数(execl
的には第二引数)は該当プログラムパスの最後の要素を渡します。使用例は以下です。
#include <stdio.h>
#include <unistd.h>
int main(void) {
int ret;
// 現在起動しているプロセスを`vi`に置き換える。
ret = execl("/bin/vi", "vi", NULL);
if (ret == -1) {
perror("execl");
return 1;
}
return 0;
}
戻り値
起動エラーの場合は-1
を返します。
起動成功した場合は関数はリターンしません。
fork
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
戻り値
fork()
の戻り値は、親プロセスは生成した子プロセスのpidとなります。
子プロセス側は0
が返されます。
生成に失敗した場合は-1
が返されます。
実行例
さっきのexecl
をベースに、新規プロセスからvi
を起動してみます。
#include <stdio.h>
#include <unistd.h>
int main(void) {
pid_t pid;
pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (!pid) {
int ret;
ret = execl("/usr/bin/vi", "vi");
if (ret == -1) {
perror("execv");
return 1;
}
}
printf("Success to create a new process.\n");
return 0;
}
exit
exit()
関数はライブラリ関数です。(システムコールではありません)
この関数はatexti()
関数やon_exit()
関数で登録された関数群を登録したのとは逆順で実行し、プロセスを終了させます。
プロセスを終了するシステムコールは_exit()
です。
#include <stdlib.h>
void exit(int status);
_exit
_exit()
システムコールはプロセスを終了させます。
ただ、前述のexit()
関数を呼び出すべきであり、直接呼んでもあまり意味はありません。
#include <unistd.h>
void _exit(int status);
atexit
#include <stdlib.h>
int atexit(void (*function)(void));
exit()
関数実行時に呼び出される関数を登録します。
登録する関数のプロトタイプは以下です。
void my_function(void);
戻り値、引数ともにありません。
on_exit
SunOS4は、atexit()
と等価なインターフェースを独自に実装しました。
Linuxでもglibcで実装されています。
#include <stdlib.h>
int on_exit(void (*function)(int, void *), void *arg);
動作はatexit()
と同じですが、登録するプロトタイプが少し異なります。
void my_function(int status, void *arg);
on_exit()
では引数を取ることができます。
wait
親プロセスが子プロセスの終了を待つケースがあります。
そのときに使うのがwait()
です。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
wait()
は終了した子プロセスのプロセスIDを返します。エラーが発生した場合は-1
です。
ゾンビプロセス
子プロセスが先に終了した場合、カーネルは親プロセスがそれを確認できるよう、プロセスを構成するのに最低限必要な情報のみを残した特別な状態に遷移させます。
これを「ゾンビ」と呼びます。
この状態のプロセスは、親プロセスから終了確認されるのを待ち、確認された時点でゾンビ状態から抜け、正式に終了します。
waitpid
複数の子プロセスを起動し、特定の子プロセスの終了を待ちたい場合があります。
その場合、wait()
では特定の子プロセスのみを監視するには向きません。
特定の子プロセスの終了を待ちたい場合はwaitpid()
を使います。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
引数のpid
には1つまたは複数のプロセスを特定する条件を渡します。
-
< -1
- プロセスグループIDがパラメータの絶対値と一致する子プロセスの終了を待つ。
- 例えば
-500
を渡した場合はプロセスグループIDが500である任意の子プロセスを意味する。
-
-1
- 任意の子プロセスの終了を待つ(つまり
wait()
と同等)
- 任意の子プロセスの終了を待つ(つまり
-
0
-
waitpid()
を発行したプロセスと同じプロセスグループに属する任意の子プロセスの終了を待つ。
-
-
> 0
- プロセスIDがパラメータに一致する子プロセスの終了を待つ。例えば
500
を渡した場合はプロセスIDが500
の子プロセスを意味する。
- プロセスIDがパラメータに一致する子プロセスの終了を待つ。例えば
system
プロセスの起動と終了待ちを行います。
#define _XOPEN_SOURCE
#include <stdlib.h>
int system(const char *command);
system()
システムコールは、プロセスを同期的に起動します。
system()
はパラメータcommand
に渡されたコマンド(パラメータ文字列も含む)を起動します。
command
の先頭には/bin/sh -c
という文字列が自動的に加えられ、文字列はすべてシェルへ渡されます。
シグナル編
kill()
システムコールは、killだけではなく任意のシグナルを送信することができる。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid
を持つプロセスに、sig
シグナルを送信します。
なお、シグナルを送るには権限が必要です。
通常は、同一ユーザーのプロセスにのみ、シグナルを送信することができます。
root権限を持つプロセスは任意のプロセスにシグナルの送信が可能です。
シグナルハンドラ
シグナルが発生した際に、そのシグナルに対してなにがしかの処理をしたい場合があります。
その際に使用されるのがシグナルハンドラです。
シグナルハンドラに設定できる関数のプロトタイプは以下です。
void my_sig_handler(int signo);
シグナルハンドラを登録するシステムコールは以下です。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signo, sighandler_t handler);
なお、SIGKILL
, SIGSTOP
は補足できず、登録しても意味がありません。
シグナルのブロック
シグナルが発生すると、プログラムは現在実行中の場所から、シグナルハンドラの場所へジャンプします。
その際、例えば処理途中でジャンプされてしまうと問題が出る処理があります。
そうした部分を「クリティカルセクション」と呼び、シグナルに割り込まれるのをブロックする仕組みがあります。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask()
の動作はhow
によって決定され、以下のいずれかの値を渡します。
-
SIG_SETMASK
カレントプロセスのシグナルマスクをset
へ変更する。 -
SIG_BLOCK
set
内にセットされたシグナルをカレントプロセスのシグナルマスクへ追加する。 -
SIG_UNBLOCK
set
内にセットされたシグナルをカレントプロセスのシグナルマスクから削除する。
String編
strcmp
#include <string.h>
int strcmp(const char *s1, const char *s2);
文字列s1
と文字列s2
を比較する。
【戻り値】
戻り値 | 意味 |
---|---|
正の値 | s1 > s2 |
負の値 | s1 < s2 |
0 | s1 == s2 |
※ 大小比較は文字コードによる。
strchr
#include <string.h>
char *strchr(const char *s, int c);
文字列s
の先頭から文字c
を探し、最初に見つかった位置をポインタで返す。見つからなかった場合はNULL
を返す。
(文字列終了コード\0
も文字列の一部とみなされる)
※ 一般的に検索する文字はint
型にcastする。
example
char test[] = "http://css-eblog.com/";
int find = (int)'o';
char *result = strchr(test, find);
printf("%s\n", result); // => "og.com/"
strstr
#include <string.h>
char *strstr(const char *s1, const char *s2);
文字列s1
の先頭から文字列s2
を検索し、見つかったらその位置のポインタを返す。見つからなかった場合はNULL
を返す。
example
char *s1 = "http://css-eblog.com/";
char *s2 = "css";
char *result = strstr(s1, s2);
printf("%s\n", result); // => "css-eblog.com/"
strcpy
#include <string.h>
char *strcpy(char *s1, const char *s2);
文字型配列s1
に、文字列s2
を\0
までコピーする。
\0
もコピーするため、それを考慮した配列サイズを確保しておく必要がある。
(s1に、s2の内容をコピーするので引数の順番に注意)
example
char *base = "http://css-eblog.com/";
char result[256];
strcpy(result, base);
printf("%s\n", result); // => "http://css-eblog.com/"
strlen
#include <string.h>
size_t strlen(const char *s);
文字列s
の長さを取得する。長さには\0
は含まれない。
memcpy
#include <string.h>
void* memcpy(void *buf1, const void *buf2, size_t n);
n
バイトメモリブロックのコピー
buf2
の先頭からn
文字分を、buf1
へコピーします。
strcpy()
との違いは\0
を付加せず、またコピー途中に\0
があってもコピーを続けます。
ヘルパー編
bzero
値0のバイトで埋める
※ 現在はmemset
を使った方法が推奨されています。bzero
は廃止予定。
#include <string.h>
void bzero(void *s, size_t n);
memset
引数buf
の先頭からn
バイト分、ch
をセットします。
#include <string.h>
void* memset(void *buf, int ch, size_t n);
※
ch
を0
にすることでbzero
と同じ結果を得ることができます。
memset(&hoge, 0, sizeof(hoge));
atoi
文字列を整数(int
)に変換します。
(ASCII to Intの略?)
#include <stdlib.h>
int atoi(const char *str);
余談
ちなみにASCIIは以下の略称のよう。初めて知った・・。
ASCII(アスキー、英: American standard code for information interchange)は、現代英語や西ヨーロッパ言語で使われるラテン文字を中心とした文字コード。これはコンピュータその他の通信機器において最もよく使われているものである。
ちなみに、int
への変換からピンと来るかもしれませんが、atol
、atof
など他の型の数値に変換する関数もあります。
gethostbyname
ホスト名をIPアドレスに変換します。
こちらの記事から引用させてもらうと、
DNS との接続や名前の解決は、全て WinSock が担当してくれます
名前の解決は gethostbyname() 関数を使います
なので、これを使うことで適切にIPアドレスに変換してくれるようです。
struct hostent FAR* gethostbyname(const char FAR *name);
ネットワーク編
socket
TCPまたはUDP通信を行う場合に、OSにソケットの作成を依頼します。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain
プロトコルファミリ。
ドメインタイプ | 説明 |
---|---|
AF_INET | インターネット(INET)ドメインの2ホスト間プロセス通信 |
AF_UNIX | UNIXドメインの1ホスト内プロセス通信。ファイルシステムソケット |
AF_ISO | IOS標準プロトコル |
AF_NS | Xerox Network Systemsプロトコル |
type
通信の種類を定義。
タイプ | 説明 |
---|---|
SOCK_STREAM | 順次双方向バイトストリーム。コネクション型の信頼性が高い通信 |
SOCK_DGRAM | データグラム。ベストエフォート型の通信 |
SOCK_ROW | 直接IPを用いた通信 |
protocol
End to Endプロトコルを指定。
プロトコルタイプ | 説明 |
---|---|
0 | 自動設定 |
IPPROTO_TCP | TCP/IP(AF_INET & SOCK_STREAMの場合。0も可) |
IPPROTO_UDP | UDP/IP(AF_INET & SOCK_DGRAMの場合。0も可) |
IPPROTO_RAW | ICMP |
ソケットの破棄
ソケット生成成功時に渡されたソケットディスクリプタの数値をclose
関数に渡します。
int sock = socket(...);
// ------
close(sock);
connect
TCP通信において、クライアントからサーバへコネクションの接続確立要求を行います。
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, size_t address_len);
-
socket
socket()で作成したソケットディスクリプタを指定します。 -
address
sockaddrへのポインタを指定します。 -
address_len
アドレス構造体のサイズを指定します。
※ 戻り値は、0
が成功、-1
が失敗。
bind
ソケットに名前をつける。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
socket()
システムコールでソケットが生成されたとき、ソケットは名前空間(アドレスファミリー)に存在するが、まだアドレスは割り当てられていません。
bind()
はファイルディスクリプタ(sockfd
)で参照させるソケットにaddr
で指定されたアドレスを割り当てます。
戻り値
成功した場合は0
を、失敗した場合には-1
が返されます。
listen
コネクションの確立時にクライアント側からの接続要求を待つため、サーバ側で使う。
保留中の要求を格納するキューをサーバで作り、接続を待つ。
#include <sys/socket.h>
int listen(int socket, int backlog);
- socket ... ソケットディスクリプタ
- backlog ... キューの長さ設定。5が一般的らしい。
戻り値
成功した場合は0
が、失敗した場合は-1
が返されます。
accept
ソケットへの接続を受ける
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
こちらを参考にさせてもらいました。
accept() システムコールは、接続指向のソケット型 (SOCK_STREAM, SOCK_SEQPACKET) で用いられる。 この関数は、接続待ちソケット socket 宛ての保留状態の接続要求が入っているキューから 先頭の接続要求を取り出し、接続済みソケットを新規に生成し、 そのソケットを参照する新しいファイルディスクリプターを返す。 新規に生成されたソケットは、接続待ち (listen) 状態ではない。 もともとのソケット sockfd はこの呼び出しによって影響を受けない。
引き数 sockfd は、 socket(2) によって生成され、 bind(2) によってローカルアドレスにバインドされ、 listen(2) を経て接続を待っているソケットである。
引数
addr
引数はsockaddr
構造体のポインタを渡し、接続相手のソケットのアドレスが格納されます。
addrlen
引数は入出力両用の引数で、呼び出し時はaddr
が指す構造体のサイズ(バイト単位)が格納されます。
戻り値
成功した場合は新しいソケットディスクリプタを、失敗時は-1
を返します。