System call & & process replacement exec for operating files

Posted by twoeyes on Fri, 14 Jan 2022 11:50:43 +0100

Tip: the following is the main content of this article. The following cases can be used for reference

1, System call

System call: few functions are used to access and control files and devices. These functions are called system calls, which are directly provided by UNIX (and Linux). They are also interfaces to the operating system itself.

System call is a method between the library and the operating system. A program calls the system call by calling the library method to realize the desired content on the operating system. Programs can also call system calls directly.

System calls related to file operations

 int open(const char* pathname, int flags);//Used to open an existing file
 
 int open(const char* pathname, int flags,mode_t mode);//Used to create a new file and set access permissions
 Parameter introduction:
 pathname: The path and name of the file to be opened
 flags : Open the sign,
 as O_WRONLY Write only open
 O_RDONLY Read only open
 O_RDWR Open read / write mode
 O_CREAT Create if file does not exist
 O_APPEND Append at the end of the file
 O_TRUNC Empty the file and write again
 mode: Permissions such as "0600"
 Return value: file descriptor

 ssize_t read(int fd, void* buf, size_t count); //Read file contents
 Parameter introduction:
 fd Corresponding open file descriptor
 buf Space for storing data
 count How many bytes of data do you plan to read from the file at a time
 Return value: the number of bytes actually read

 ssize_t write(int fd, const void* buf,size_t count); //Write to file
 Parameter introduction:
 fd Corresponding open file descriptor
 buf Store data to be written
 count Plan how much data to write to the file at a time

 int close(int fd); //Close file
 Parameter introduction:
 fd File descriptor to close

File descriptor:
The file descriptor is a data of type int.

Ordinary users cannot directly access the files in the kernel. Users can only access the data in the kernel through system calls. In order for the kernel to know which file to access, it is necessary to tell the kernel the ID generated after the program is started. After the kernel gets the ID, it can find the corresponding file and operate the file accordingly. This ID is the file descriptor.

Analysis: the parent process opens a file first, and the child process can share it after fork ing?

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
 #include <assert.h>
 #include <fcntl.h>
 //Print results of analysis program execution
 int main()
 {
 char buff[128] = {0};
 //myfile.txt create it yourself and write the content as "abcdef"
 int fd = open("myfile.txt",O_RDONLY);

 pid_t pid = fork();
 assert( pid != -1 );

 if ( pid == 0 )
 {
 read(fd,buff,1);
 printf("child buff=%s\n",buff);

 sleep(1);
 read(fd,buff,1);
 printf("child buff=%s\n",buff);
 }
 else
 {
 read(fd,buff,1);
 printf("parent buff=%s\n",buff);

 sleep(1);
 read(fd,buff,1);
 printf("parent buff=%s\n",buff);
 }

 close(fd);
 }

Since the PCB of the child process created by fork copies the parent process, and the pointer of the file table in the PCB of the child process to the open file only copies the value in the PCB of the parent process, the parent and child processes will share all the file descriptors opened by the parent process before fork. As shown in the figure below:

Exercise: write a program to copy an ordinary file (similar to cp command)

 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <fcntl.h>
 #include <unistd.h>
 /*
 *Copy an ordinary file named "passwd" in the current directory. The new file is called "newpasswd"
 */
 int main()
 {
 char buff[256] = {0};
 int fdr = open("passwd",O_RDONLY);
 assert( fdr != -1 );

 int fdw = open("newpasswd",O_WRONLY|O_CREAT,0600);
 assert( fdw != -1 );

 int n = 0;

 while( (n = read(fdr,buff,256)) > 0 )
 {
 write(fdw,buff,n);
 }

 close(fdr);
 close(fdw);
 }

2, Differences between system calls and library functions

Difference: the implementation of system call in the kernel belongs to kernel space, and the implementation of library function in function library belongs to user space.

The execution process of system call is as follows:

3, Process replace exec

(1) Introduction to exec System Functions

exec series replacement process: pcb is replaced with the previous process entity.

 #include <unistd. h> / / header file to be referenced
 
 exec Introduction to family methods execl take as an example:
 /*
 *path:The path name of the newly replaced program
 *arg :The first parameter passed to the main function of a new program, usually the name of the program
 *arg This is followed by the list of remaining parameters. The number of parameters is variable, and the null pointer must be used as the last parameter
 */
 int execl(const char* path, const char * arg,...);
 
 int execlp(const char* file, const char * arg,...);
 
 int execle(const char* path, const char * arg,...,char* const envp[]);
 
 int execv(const char * path, char* const argv[]);
 
 int execvp(const char * file, char* const argv[]);
 
 int execve(const char * path, char* const argv[],char* const envp[]);

(2) Use of exec System Functions

Taking ps replacing the current program as an example, this paper introduces the use of exec System functions. Note that the functions of this series of methods are the same, there is no difference, but the parameters are different

The code example used by execl is as follows: (ps replaces the current program)

Example 1:

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <assert.h>
 #include <string.h>

 int main()
 {
 printf("main pid=%d\n",getpid());
 //execl is executed successfully without returning. It is directly executed from the main function of the new program. The error code is returned only if it fails
 execl("/bin/ps","ps","-f"(char*)0);
 perror("execl error");
 exit(0);
 }

Example 2:

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <assert.h>
 #include <string.h>

 int main()
 {
 //Store the parameters passed to the main function of the new program
 char* myargv[] = {"ps","-f",(char*)0};
 //Store the environment variables passed to the main function of the new program
 char* myenvp[] = {"MYSTR=hello","VAL=100",(char*)0};

 printf("main pid=%d\n",getpid());

 //execl does not return if it is executed successfully, but the error code is returned if it fails
 execve("/bin/ps",myargv,myenvp);
 perror("execve error");

 exit(0);
 }

(3) fork and exec are used together to create a new process

In the following example, the current main program copy generates a sub process, and the sub process replaces itself with the new program "b".

The current main program is as follows:

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <assert.h>
 #include <string.h>
 #include <fcntl.h>
 #include <errno.h>

 int main(int argc, char* argv[],char* envp[])
 {
 printf("main pid=%d\n",getpid());

 pid_t pid = fork();
 assert( pid != -1 );

 if ( pid == 0 )
 {
 char * myargv[] = {"b","hello","abc","123",(char*)0};
 //char * myenvp[] = {"MYSTR=hello","VAL=100",(char*)0};

 execve("./b",myargv,envp);
 perror("execl error");
 exit(0);
 }

 wait(NULL);
 printf("main over\n");

 exit(0);
 }

The newly replaced b procedure is as follows:

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <assert.h>
 #include <string.h>


 int main(int argc, char* argv[],char* envp[])
 {
 printf("b pid=%d\n",getpid());

 int i = 0;
 printf("argc=%d\n",argc);

 for( ; i < argc; i++ )
 {
 printf("argv[%d]=%s\n",i,argv[i]);
 }

 for( i = 0; envp[i] != NULL; i++ )
 {
 printf("envp[%d]=%s\n",i,envp[i]);
 }

 exit(0);
 }

Topics: Linux