System Calls and Command Line Arguments

Session 0X03

Screenshot from 2022-09-26 16-16-00.png

Imagine we don't have OS or Kernel, and we have a powerful machine connected to many devices (scanner, printer, ethernet, use, ...) and there are many users want to use the machine.

If there isn't an OS, a user will run a specific program, then the program will directly access hard desk. What id there is a program needs the output of another, who can manage all these resources and connecting various devices to correct program. This is impossible without OS.

To manage all of these situations we need an OS.

Screenshot from 2022-09-26 16-21-48.png

With the help of the Kernel, a process will accomplish its job. Also the user will use the process through the Kernel and the Kernel will communicate with the devices as it has their drivers.

System Call

System call is the communication method between the process and the Kernel. It is an entry point into the Linux kernel.

If a process needs to do a service (privilege Service) such as accessing a hardware (not just a logic) like write on screen, read from usb, speak to another process, ... etc. It will not do this directly, it will ask the kernel to do that.

There is an interface (system call) between the Application and the Kernel. If an app need to do something, it will call the kernel and the kernel will do the task.

Screenshot from 2022-09-26 16-33-57.png

System call may differ from kernel to another, or from Unix to another. However, they have a standard to be Unix like operating system. Some systems may have system calls that don't exist in another. Command man syscalls gives you the systems calls in the system.

C Library

C library in Linux is a collection of files containing a lot of functions that are needed by any OS or App to work its task. Ex. printf() function.

C library has two types of functions:

Normal Function

This type is working with user data like copying string from buffer to another, here we don't need the kernel to copy from memory space to another in the user App space.

Wrapper Function

System call isn't user friendly function to be used in an application. So, there is this type of wrapper function on the system call with added functionality.

If you want to read date from (terminal, screen, UART), you call a system call to read from standard input.

So, this type of function is not only a C code executed in the user space. It will do some tasks, then contact the kernel through a system call to accomplish a specific task and return to the C library then to the user.

Process Files

Any process when running, kernel opens three files for it:

  1. Standard Input (source of input comes from)
  2. Standard Output (default file for the output of the process)
  3. Standard Error (if error occurred, it will be printed in this file)

Each file is identified by file descriptor or file ID.

Examples on Files and System Calls

Let us print on screen by using a system call, we will use write system call. Command man 2 write will gives you the information you need to use write system call wrapper.

The prototype of write is ssize_t write(int fd, const void *buf, size_t count);

  • fd is the file descriptor
  • *buf is an array of bytes
  • count is the number of bytes in the buffer
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define RET 0

int main()
{
    char *buf;
    buf = "Hello Linux\n";  

    int count = strlen(buf);   

    write(1, buf, count);   

    return RET;
}

Screenshot from 2022-09-26 17-04-20.png

Function write is not the system call itself, it is a wrapper function on the system call. Calling a system call directly is a complex process.

How to know the system calls that are used by a function?

Command stacre with the file name can do that strace ./myhello

Lets take another example and use another system call open. In this example we will write the output of our process in a file not the screen.

The prototype of open is int open(const char *pathname, int flags);

  • pathname is the file path
  • flags is what you need when opening the file like (Read only, read/write, create, ....)

Function open returns the file descriptor on success.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#define RET 0

int main()
{
    char *buf;
    buf = "Hello Linux\n";

    int count = strlen(buf);

    int fd = open("./open_hello.txt", O_WRONLY|O_CREAT, 0644);

    printf("FD is %d\n", fd);

    write(fd, buf, count);

    close(fd)

    return RET;
}

Screenshot from 2022-09-26 17-35-02.png

Command Line Arguments

We knew that every command support options like ls -l and arguments like ls / or ls / /home.

Sell takes the command and execute it, to execute a command it needs to parse the command line arguments.

As an example the below command has four arguments:

ls / /home /var
  1. ls
  2. /home
  3. /home
  4. /var

How the process takes the command line arguments?

ls is an executable file on the hard disk. which ls, file /usr/bin/ls.

When you write ls in terminal, shell construct the ls process and load it in memory to execute.

How ls process was passed arguments?

When we construct a process, we puts its command line arguments on its stack.

Screenshot from 2022-09-26 17-49-07.png

The first thing loaded on stack is argc and argv which are the command line arguments. And this is the way how the kernel passes the arguments that were invoked in the command line to the process.

Why we need to give the process the command line arguments?

Because the behavior of the process is dependent on these arguments.

main is not main

In Embedded Systems, the startup code initialize the main function. While we are creating a void main int main(void), it is actually have arguments int main(int argc, char *argv[]) when initialized by the startup code.

  • int argc: the number of arguments
  • char *argv[]: the arguments are passed as an array of strings

bash divide commands to tokens based on white spaces in between. By convention argv[] ends with a null pointer. Then if argc is 2, size of argv[] is 3 pointers.

Screenshot from 2022-09-26 18-23-31.png

Any function argument is brought from the stack.

Let's write a program to print the command line arguments.

#include <stdio.h>

#define RET 0

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

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

    return RET;
}

Screenshot from 2022-09-26 18-53-12.png

Now we know that when Kernel run a process, it gives it the command line arguments the were passe when running it. And if the process need to do some task with the system, it calls a system call.

Lets take another example and write our cp program.

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define RET 0

int main(int argc, char *argv[])
{
        char buf[100];

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

        if(argc != 3)
        {
                return -1;
        }

        int fd1 = open(argv[1], O_RDONLY);
        int fd2 = open(argv[2], O_WRONLY|O_CREAT, 0644);

        int count = read(fd1, buf, 100);
        write(fd2, buf, count);

        close(fd1);
        close(fd2);

        return RET;
}

Screenshot from 2022-09-26 19-16-08.png