What is fopen() and open() in Linux?

The key difference between the fopen() and the open() function in the Linux operating system is that the open() function is a low-level call, where the fopen() when called simply calls the open() function in the background and it returns a Filepointer directly.
The call to the open() function includes invoking several other functions and the behaviour of the entire process is mentioned below as a reference to understand the open() function better.
Consider the code shown below −
int sys_open(const char *filename, int flags, int mode) {
   char *tmp = getname(filename);
   int fd = get_unused_fd();
   struct file *f = filp_open(tmp, flags, mode);
   fd_install(fd, f);
   putname(tmp);
   return fd;
}
The above code can also be found inside the fs/open.c file on your linux machine.
Now, as we can see there are many functions that gets called from this function, like the first of them is the function named getname() in which we pass the filename as an argument and the code of the getname() function looks something like this −
#define __getname() kmem_cache_alloc(names_cachep, SLAB_KERNEL)
#define putname(name) kmem_cache_free(names_cachep, (void *)(name))

char *getname(const char *filename) {
   char *tmp = __getname(); /* allocate some memory */
   strncpy_from_user(tmp, filename, PATH_MAX + 1);
   return tmp;
}
The above code can be found inside the fs/namei.c file and its main use is to copy the file name from the user space and pass it to the kernel space. Then after the getname() function we have the get_unused_fd() function which returns us an unused file descriptor, which is nothing but an integer index into a growable list of currently opened files. The code of the get_unused_fd() function looks something like this −
int get_unused_fd(void) {
   struct files_struct *files = current->files;
   int fd = find_next_zero_bit(files->open_fds, files->max_fdset, files->next_fd);
   FD_SET(fd, files->open_fds); /* in use now */
   files->next_fd = fd + 1;
   return fd;
}
Now we have the filp_open() function that has the following implementation −
struct file *filp_open(const char *filename, int flags, int mode) {
   struct nameidata nd;
   open_namei(filename, flags, mode, &nd);
   return dentry_open(nd.dentry, nd.mnt, flags);
}
The above function plays two key roles, first, it uses the filesystem to look up the inode which corresponds to the filename of path that was passed in. Next, if creates a struct file with all the essential information about the inode and then returns the file.
Now, the next function in the call stack is the fd_install() function which can be found in the include/linux/file.h file. It is used to store the information returned by the function filp_open(). The code for the fd_install() function is shown below −
void fd_install(unsigned int fd, struct file *file) {
   struct files_struct *files = current->files;
   files->fd[fd] = file;
}
Then we have the store() function that stores the struct that was returned from the filp_open() function and then installs that struct into the process’s list of open files.
The next step is to free the allocated block of kernel-controlled memory. Finally, it returns the file descriptor, which can then be passed to other C functions like close(), write(), etc.