فصل ۵ - File I/O: Further Details

atomicity is a necessary requirement for the correct operation of many system-
calls. ayomicity is a concept that we will encounter repeatedly, when
discussing the operation of system calls. All system calls are executed
atomically. by this, we mean that the kernel quarantees that all of the steps
in a system call are completed as a single operation, without being interrupted
by another process or thread.

fcntl() is a system call.

Specifying O_EXCL in conjunction with O_CREAT causes open() to return an error
if the file already exists. This provides a way for a process to ensure that
it is the creator of a file.

sleep() causes the calling thread to sleep either until the number of real-
time seconds specified in seconds have elapsed or until a signal arrives which
is not ignored.

+---------------------------------------------------------------------------+
| #include <unistd.h>                                                       |
|                                                                           |
| unsigned int sleep(unsigned int seconds);                                 |
|                                                                           |
|   return 0 if requested time elapsed, or number of seconds left to sleep  |
|                                                                on signal  |
+---------------------------------------------------------------------------+

... again, this is a race condition because the results depend on the order of
scheduling of the two processes.

O_APPEND guarantes that seek to the next byte past the end of the file and the
write operation happend automatically.

+---------------------------------------------------------------------------+
| #include <fcntl.h>                                                        |
|                                                                           |
| int fcntl(int fd, int cmd, ...);                                          |
|                                                                           |
|                          return on success depends on cmd, or -1 on error |
+---------------------------------------------------------------------------+

the fcntl() system call performs a range of operations on an open file
descriptor.

    ! accessMode = flags & O_ACCMODE;
    ! if (accessMode == O_WRONLY) { ... }

F_GETFL
F_SETFL
F_DUPFD
F_DUPFD_CLOEXEC (since linux 2.6.24) SUSV4

It is possible and useful to have multiple descriptors refering to the same
file. these file descriptors may be open in the same process of in different
processes.

3 data structures maintained by the kernel: (p. 95)
    1- per process file descriptor table (file descriptor table)
    2- system wide table of open file descriptors (open file table)
    3- file system i-node table

+ on-disk i-node
+ in-memory inode

One process can pass an open file descriptor to another process using a UNIX
domain socket.

open file status flags: O_APPEND, O_NONBLOCK, O_ASYNC

O_CLOEXEC is a per process file descripor flag.

...  one reason for this is that the two file descriptors would not share a
file offset pointer, and hence could end up overwriting each other's output.
Another reason is that the file may not be a disk file:
    ./myscript 2>&1 | less

The new descriptor is guaranteed to be the lowest unused file descriptor.
+---------------------------------------------------------------------------+
| #include <unistd.h>                                                       |
|                                                                           |
| int dup(int oldfd);                                                       |
|                                                                           |
|                    Returns new file descriptor on success, or -1 on error |
+---------------------------------------------------------------------------+

with dup2() we can determine new file descriptor number
+---------------------------------------------------------------------------+
| #include <unistd.h>                                                       |
|                                                                           |
| int dup2(int oldfd, int newfd);                                           |
|                                                                           |
|                                  Returns newfd on success, or -1 on error |
+---------------------------------------------------------------------------+

if the file descriptor specified in newfd is already open, dup2() closes it
first. any error that occurs during this close is silently ignored.

    newfd = fcntl(oldfd, F_DUPFD, startfd);

+---------------------------------------------------------------------------+
| #define _GNU_SOURCE                                                       |
| #define <unistd.h>                                                        |
|                                                                           |
| int dup3(int oldfd, int newfd, int flags);                                |
|                                                                           |
|                       Returns new file descriptor on success, -1 on error |
+---------------------------------------------------------------------------+

currently dup3() supports one flag, O_CLOEXEC, which causes the kernel to
enable the close-on-exec flag (FD_CLOEXEC) for the new file descriptor.

pread() and pwrite() system calls just like read() and write(), except that
file I/O is performed at the location specified by offset. the file offset
is left unchanged by these calls.
+---------------------------------------------------------------------------+
| #include <unistd.h>                                                       |
|                                                                           |
| ssize_t pread(int fd, void *buf, size_t count, off_t offset);             |
|                    Returns number of bytes read, 0 on EOF, or -1 on error |
|                                                                           |
|      ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); |
|                           Returns number of bytes written, or -1 on error |
+---------------------------------------------------------------------------+

these system calls can be particularly useful in multithreaded applicatations.
در اینجا عملیات تغییر آفست فایل و نوشتن یا خواندن از فایل و سپس بازنشانی آفست
اصلی فایل به صورت اتمیک انجام میشود. به طور کلی این سیستم کالها در هر شرایطی
که درایه open files table بین چند پراسس یا ترد مشترک باشد مفید است.

Instead of accepting a single buffer of data to be read or written, these
functions transfer multiple bytes of data in a single system call.

+---------------------------------------------------------------------------+
| #include <sys/uio.h>                                                      |
|                                                                           |
| ssize_t readv(int fd, const struct iovec *iov, int iovcnt);               |
| Returns number of bytes read, 0 on EOF, or -1 on error                    |
|                                                                           |
| ssize_t writev(int fd, const struct iovec *iovm int iovcnt);              |
|                           Returns number of bytes written, or -1 on error |
+---------------------------------------------------------------------------+

    struct iovec {
        void *iov_base;     /* start address of the buffer */
        size_t iov_len;     /* number of bytes to transfer to/from buffer */
    }

sizeof(strcut stat) = 144 byte

readv() and writev() system calls are atomics.

+---------------------------------------------------------------------------+
| #define _BSD_SOURCE                                                       |
| #include <sys/uio.h>                                                      |
|                                                                           |
| ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);|
|                   Returns number of bytes read, 0 on EOF, or -1 on error  |
|                                                                           |
| ssize_t pwritev(int fd, const struct iovec *iov, int iobcnt, off_t offset)|
|                           Returns number of bytes written, or -1 on error |
+---------------------------------------------------------------------------+

truncate() and ftruncate() system calls set the size of a file to the value
specified by length. file must be accessible and writable.
+---------------------------------------------------------------------------+
| #include <unistd.h>                                                       |
|                                                                           |
| int truncate(const char *pathname, off_t length);                         |
| int ftruncate(int fd, off_t length);                                      |
|                                  Both return 0 on success, or -1 on error |
+---------------------------------------------------------------------------+

ftruncate() will not change file offset of the file.

    $ man 7 standards

The truncate() system call is unique in being the only system call that can
change the contents of a file without first obtaining a descriptor for the file
via open()  (or by some other means).

One case where open() can block is with FIFOs.

EWOULDBLOCK=EAGAIN=Resource temporarily unavailable

O_NONBLOCK is generally ignored for regular files, because the kernel buffer
cache ensures that I/O on regular files doesn't block. (?-p.103)

I/O on large files in 32-bit systems --> Large file summits (LFS)
beacause off_t in 32-bit systems is a signed integer naturally our files can
have about 2GB size.

in 32 bit systems:
    1- #define _LARGEFILE64_SOURCE feature test macro when compiling our
       program, either on the command line, or within the source file. it
       enables 64 bit version of many functions such as open64(), lseek64(),
       stat64(), truncate64(), mmap64(), fopen64(), ...
       In order to access a large file, we simply use the 64-bit version of
       this functions.
       off64_t, struct stat64 data types
       lseek64() returns long long integer
    2- The recommended method of obtaining LFS functionality is to define the
       macro _FILE_OFFSET_BITS with the value 64

       $ cc -D_FILE_OFFSET_BITS=64 proc.c in comamnd line when compiling.
       or
       #define _FILE_OFFSET_BITS 64 in source code

       This automatically converts all of the relevant 32-bit functions and
       data types into their 64-bit counterparts.

open64() = open(, O_LARGEFILE)

portable method for displaying values of one of the predefined system data types
(e.g., pid_t or uid_t) is to cast the value to long.

to display a value of off_t data type in a portable manner we must cast it to
long long and use the %lld printf specifier.

for each process, the kernel provides the special virtual directory /dev/fd
which contains all open file descriptors of that process.

opening one of the files in the /dev/fd directory is equivalent to duplicating
the corresponding file descriptor.

    /dev/fd -> /proc/self/fd -> /proc/PID/fd

some programs interpret a single hyphen (-) as a delimiter making the end of
command line options.

$ ls | diff - oldfilelist  == $ ls | diff /dev/fd/0 oldfilelist

mkstemp() is a library function.
+---------------------------------------------------------------------------+
| #include <stdlib.h>                                                       |
|                                                                           |
| int mkstemp(char *template)                                               |
|                        Returns file descriptor on success, or -1 on error |
+---------------------------------------------------------------------------+
the template string must be terminated with 6 'X' character. mkstemp() will
return the modified template, therefore template must be defined as a character
array and not a string constant.

mkstemp() create the file with 0600 permission and O_EXCL flag, guaranteeing
that the caller has exclusive access to the file.

+---------------------------------------------------------------------------+
| #include <unistd.h>                                                       |
|                                                                           |
| int unlink(const char *pathname);                                         |
|                                      Returns 0 on success, or -1 on error |
+---------------------------------------------------------------------------+

+---------------------------------------------------------------------------+
| #include <stdio.h>                                                        |
|                                                                           |
| FILE *tmpfile(void);                                                      |
|                         Returns file pointer on success, or NULL on error |
+---------------------------------------------------------------------------+
the temprary file created with tmpfile(), is automatically deleted when the
program terminate.

The numbered files in the /dev/fd virtual directory allow a process to access
its own open files via file descriptor numbers, which can be particularly
useful in shell commands.

مثال‌های این فصل

نوشته شده در: 1405-03-10 (1 هفته 21 ساعت 50 دقیقه پیش)

من محسن هستم؛ برنامه‌نویس تفننی!

برای ارتباط با من یا در همین سایت کامنت بگذارید و یا به dokaj.ir(at)gmail.com ایمیل بزنید.

در مورد این مطلب یادداشتی بنویسید.