File opening in linux is handled directly by the kernel but there's several things that you can do to influence and study the process.

System calls
Starting from the top, you can see that the interface applications use to interact with files is system calls.
Open, read and write do what you expect, while stat returns information about a file without opening it.
You can study a program's usage of file-related syscalls using strace:
$ strace -e trace=%file /bin/ls /etc
[...]
stat("/etc", {st_mode=S_IFDIR|0755, ...}) = 0
openat(AT_FDCWD, "/etc", O_RDONLY...) = 3
This analyses the syscalls caused by ls /etc
, showing that stat
and openat
are called on the /etc
directory.
You might be wondering why we're calling file operations on a directory. In UNIX, directories are files too. In fact everything is a file!
File descriptors
You might be wondering about the openat() = 3
in the output above.
In UNIX opened files are represented by a file descriptor, which is a unique representation of the open file by a certain process. File descriptors 0, 1 and 2 are usually reserved for the standard streams (user input/output), so the first open file will be 3.
You can get a list of open file descriptors for a given process by using lsof
(list open files):
$ cat /dev/urandom > /dev/null &
[1] 3242
$ lsof -p 3242
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
...
cat 3242 user 0u CHR 136,0 0t0 3 /dev/pts/0
cat 3242 user 1w CHR 1,3 0t0 1028 /dev/null
cat 3242 user 2u CHR 136,0 0t0 3 /dev/pts/0
cat 3242 user 3r CHR 1,9 0t0 1033 /dev/urandom
The FD
column shows you the file descriptor number, along with the access.
You can also use fuser
to search for processes that hold particular files:
$ fuser /dev/urandom
/dev/urandom: ... 3242 ...
Process information pseudo-filesystem - /proc
By now you might be wondering: but how does lsof
know which files are open in the first place?
Well, let's take a look!
$ strace -e trace=%file lsof -p 3242
...
stat("/proc/3242/", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/3242/stat", O_RDONLY) = 4
...
openat(AT_FDCWD, "/proc/3242/fd", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
readlink("/proc/3242/fd/0", "/dev/pts/0", 4096) = 10
lstat("/proc/3242/fd/0", {st_mode=S_IFLNK|0700, st_size=64, ...}) = 0
stat("/proc/3242/fd/0", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "/proc/3242/fdinfo/0", O_RDONLY) = 7
...
So lsof
knows knows which files are open by... reading more files! Specifically, the directory /proc/3242/fd
. Everything under /proc
is a "fake" filesystem kept by the kernel. You can ls -l
it to see it's structure.
Influencing file opening
There's several methods you can use to influence file opening, although they aren't as easy as just replacing some script.
If you're looking to change the way files are stored or accessed, like providing encryption, caching it, spreading it across multiple disks, or something similar, there's a good chance that there's already a existing device mapper that suits your needs.
If you want fine-grained control over file opening in a particular directory / mount, you can write a simple FUSE filesystem and mount it.
At the program/process level, you can use LD_PRELOAD to change the C library calls and prevent them from doing the normal syscalls.
The hardest but most flexible way would be writing your own filesystem driver.
ext
here unix.stackexchange.com/q/652047/140633 - that, in combination with the links in comments might be of interest.open()
system call, right? (And read/write on the file descriptor, same as for reading/writing on the TTY. And for the redirect, adup2
). Scripts involve running files, so I don't see a sensible way for that to work without a chicken/egg problem. Not to mention that dynamically-linked executables can't even start up without accessing a bunch of files, and even a static executable is part of the filesystem. So PID=1init
involves reading files, and fork/exec of them.init
depends on the ability to open/read files to already exist, for it to even be able to start. Nothinginit
does enables that; that's all in the kernel, as the answers explain. The filesystem has to work for the kernel to even be able to start/sbin/init
(init=/foo/bar
) as PID=1, the first user-space task.