ファイル記述子 (File Descriptor) に関連するシステムコールを利用した C 言語のサンプルコードを記載します。
main.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd_r, fd_w;
// 読み込み専用
if((fd_r = open("./main.c", O_RDONLY)) < 0) {
perror("xxx");
return 1;
}
// 存在しなければ新規作成して書き込み、存在していれば既存の内容を削除して書き込み
// 8進数666 - umask でファイル作成 (umask 022 ならば 644 で作成)
if((fd_w = open("/tmp/sample.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
// 追記する場合は O_WRONLY|O_APPEND
perror("xxx");
return 1;
}
if(close(fd_r) < 0) {
perror("xxx");
return 1;
}
if(close(fd_w) < 0) {
perror("xxx");
return 1;
}
return 0;
}
実行例 (関連: umask)
$ gcc -Wall -O2 main.c && ./a.out
$ ls -l /tmp/sample.txt
-rw-r--r-- 1 vagrant vagrant 0 Jul 31 15:31 /tmp/sample.txt
$ umask
0022
#include <unistd.h>
#include <stdio.h>
int main() {
char buf[1024];
ssize_t n;
if((n = read(0, buf, sizeof buf)) < 0) {
perror("xxx");
return 1;
}
write(1, buf, n);
return 0;
}
実行例
$ gcc -Wall -O2 main.c && echo 123 | ./a.out
123
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd;
off_t offset;
ssize_t n;
char buf[1024];
if((fd = open("./main.c", O_RDONLY)) < 0) {
perror("open failed");
return 1;
}
// 400 バイト進める
if((offset = lseek(fd, 400, SEEK_SET)) < 0) {
perror("lseek failed");
return 1;
}
if((n = read(fd, buf, sizeof buf)) < 0) {
perror("read failed");
return 1;
}
write(1, buf, n);
return 0;
}
実行例
$ gcc -Wall -O2 main.c && ./a.out
izeof buf)) < 0) {
perror("read failed");
return 1;
}
write(1, buf, n);
return 0;
}
#include <unistd.h>
#include <stdio.h>
int main() {
if(truncate("sample.txt", 0) < 0) { // 0 バイトに切り詰める
perror("truncate failed");
return 1;
}
return 0;
}
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// mkdir/rmdir
if(mkdir("mydir", 0777) < 0) { // 777 - umask
perror("mkdir failed");
return 1;
}
if(rmdir("mydir") < 0) {
perror("rmdir failed");
return 1;
}
// rename/unlink
if(rename("sample.txt", "sample2.txt") < 0) {
perror("rename failed");
return 1;
}
if(unlink("sample2.txt") < 0) {
perror("unlink failed");
return 1;
}
return 0;
}
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
if(chmod("sample.txt", 0777) < 0) { // umask は無関係
perror("chmod failed");
return 1;
}
if(chown("sample.txt", 123, 456) < 0) { // uid, gid
perror("chown failed");
return 1;
}
return 0;
}
#include <unistd.h>
#include <stdio.h>
int main() {
ssize_t n;
char buf[1024];
if(symlink("./main.c", "mylink.c") < 0) { // mylink.c -> ./main.c
perror("symlink failed");
return 1;
}
if((n = readlink("mylink.c", buf, sizeof buf - 1)) < 0) {
perror("readlink failed");
return 1;
}
buf[n] = '\0';
printf("%s\n", buf); // "./main.c"
return 0;
}
シェルのパイプでも利用されているシステムコールです。例えば fork して作成した子プロセスからの書き込みを親プロセスで受け取ることができます。プロセス間通信 IPC (inter process communication) の実装で利用できます。
#include <unistd.h>
#include <stdio.h>
int main() {
// 親プロセスと子プロセスで利用する、
// 接続されたファイル記述子を格納します。
int pipe_fd[2];
// 必須ではありませんがここでは子プロセスを生成します。
pid_t child_pid;
// 親プロセスでファイル記述子からデータを読み出すために利用します。
ssize_t n;
char buf[4096];
// ファイル記述子を二つ作成して接続します。
if(pipe(pipe_fd) < 0) {
perror("pipe failed");
return 1;
}
printf("%d => %d\n", pipe_fd[1], pipe_fd[0]);
// 子プロセスを作成します。
if((child_pid = fork()) < 0) {
perror("fork failed");
return 1;
}
else if(child_pid == 0) {
// 子プロセスの場合の分岐
close(pipe_fd[0]); // 使用しないため閉じます。
write(pipe_fd[1], "IPC from child\n", 15); // IPC (inter process communication) プロセス間通信
_exit(0);
}
// 親プロセスの場合の分岐
close(pipe_fd[1]); // 使用しないため閉じます。
if((n = read(pipe_fd[0], buf, sizeof buf)) < 0) {
perror("read failed");
return 1;
}
write(1, buf, n);
return 0;
}
実行例
$ gcc -Wall -O2 main.c && ./a.out
4 => 3
IPC from child
#include <stdio.h>
#include <poll.h>
int main() {
int n;
// 簡単のため、ファイル記述子0 (標準入力) だけを監視してみます。
struct pollfd fds[1];
char buf[256];
fds[0].fd = 0;
fds[0].events = POLLIN;
while(1) {
n = poll(fds, 1, 5000);
if(n < 0) {
perror("poll");
return 1;
}
else if(n == 0) {
printf("no input\n");
}
else {
if(fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
fprintf(stderr, "error\n");
return 1;
}
if(fds[0].revents & POLLIN) {
printf("input from fd0\n");
fgets(buf, 256, stdin); // 標準入力から一行読み込んで空にする。
}
}
}
return 0;
}
実行例
$ gcc -Wall -O2 main.c && ./a.out
a ←エンター
input from fd0
aaa
input from fd0
no input ←5秒経過
poll と異なり select では監視できるファイル記述子数に制限があります。
#include <sys/select.h>
#include <stdio.h>
int main() {
int n;
fd_set readfds;
struct timeval tv;
// ファイル記述子 0 と 3 を監視
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(3, &readfds);
// 5 秒でタイムアウトするように設定
tv.tv_sec = 5;
tv.tv_usec = 0;
// select では監視できるファイル記述子数に制限があります。
printf("SETSIZE = %d\n", FD_SETSIZE);
while(1) {
// ファイル記述子 0 から 4-1 までを監視
n = select(4, &readfds, NULL, NULL, &tv);
if(n < 0) {
perror("select failed");
return 1;
}
else if(n == 0) {
printf("no input\n");
}
else {
if(FD_ISSET(0, &readfds)) {
printf("fd = 0\n");
}
if(FD_ISSET(3, &readfds)) {
printf("fd = 3\n");
}
}
}
return 0;
}
実行例
gcc -Wall -O2 main.c && ./a.out 3<&0
以下のサンプルではファイル記述子1 (標準出力) の複製であるファイル記述子3 を生成して利用しています。
#include <unistd.h>
#include <stdio.h>
int main() {
if(dup2(1, 3) < 0) {
perror("dup2 failed");
return 1;
}
write(3, "Hello\n", 6);
return 0;
}
同様のことは汎用的なファイル記述子操作用のシステムコール fcntl に F_DUPFD
を指定しても実現できます。
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
if(fcntl(1, F_DUPFD, 3) < 0) {
perror("fcntl failed");
return 1;
}
write(3, "Hello\n", 6);
return 0;
}
実行例
$ gcc -Wall -O2 main.c && ./a.out
Hello
デバイスファイルには、実際に接続されているハードウェアのデバイスドライバへのインタフェースとして機能するファイルや、擬似デバイスの /dev/null
等を含めて以下のようなものがあります。
/dev/sda1 (ハードディスク)
/dev/null
/dev/zero
/dev/stdin -> fd/0
/dev/stdout -> fd/1
/dev/stderr -> fd/2
/dev/tty (接続している端末デバイス teletypewriter TTY)
/dev/pts/{番号} (擬似端末)
/dev/port (I/O ポートアクセス)
疑似端末 (pseudo TTY) は SSH 等でリモートログインすると生成されます。以下では SSH クライアントが 4 つ接続している状態です。
vagrant@stretch:~$ w
14:15:03 up 2:36, 4 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
vagrant pts/0 10.0.2.2 11:39 51:25 0.20s 0.20s -bash
vagrant pts/1 10.0.2.2 13:34 38:46 0.07s 0.02s pager
vagrant pts/2 10.0.2.2 13:37 2:04 0.12s 0.00s less
vagrant pts/3 10.0.2.2 14:08 0.00s 0.03s 0.00s w
vagrant@stretch:~$ ls -l /dev/pts/
total 0
crw--w---- 1 vagrant tty 136, 0 Aug 12 13:23 0
crw--w---- 1 vagrant tty 136, 1 Aug 12 13:36 1
crw--w---- 1 vagrant tty 136, 2 Aug 12 14:13 2
crw--w---- 1 vagrant tty 136, 3 Aug 12 14:15 3
c--------- 1 root root 5, 2 Aug 12 11:38 ptmx
自分自身の番号は tty
コマンドで確認できます。
vagrant@stretch:~$ tty
/dev/pts/3
以下の三つは同じ挙動を示します。
echo 123
echo 123 > /dev/tty
echo 123 > /dev/pts/3
ps
コマンドの TTY 列で各プロセスの制御端末の番号を確認できます。
vagrant@stretch:~$ ps
PID TTY TIME CMD
23929 pts/3 00:00:00 bash
23971 pts/3 00:00:00 ps