Writing application instances does not belong to the Linux operating system transplantation. However, to ensure the integrity of this series of articles, we provide a series of instances for developing applications for Embedded Linux.
The following tools are required to write Linux applications:
(1) compiler: gcc
GCC is the most important development tool in Linux. It is the gnu c and C ++ compilers. Its basic usage is GCC [Options] [filenames].
We should use arm-Linux-GCC.
(2) Debugger: GDB
GDB is a powerful debugger used to debug C and C ++ programs. We can use it to perform a series of debugging tasks, including setting breakpoints, viewing variables, and performing a single step.
We should use arm-Linux-GDB.
(3) make
The main task of GNU make is to read a text file called makefile. This file records which files are generated and what commands are used to generate them. Make checks files on the disk based on the information in this makefile. If the creation or modification time of the target file is earlier than that of one of its dependent files, make executes the corresponding command, to update the target file.
The compilation rules in makefile must use the arm-Linux-version accordingly.
(4) code editing
You can use the traditional VI editor, but it is best to use Emacs, which has syntax highlighting, version control, and other ancillary functions.
After developing an application using the above tools on the host machine, you can download the program to the target board to run it through the following channels:
(1) download the program to the file system of the Target Board through the serial communication protocol RZ (thanks to a command like RZ provided by Linux );
(2) download the program from the FTP directory on the host through the FTP communication protocol to the file system on the target board;
(3) swap the program into the USB flash drive, mount the USB flash drive on the target machine, and run the program in the USB flash drive;
(4) If the target machine Linux uses the NFS file system, you can directly upload the program to the corresponding directory of the host machine, which can be directly used in the target machine Linux.
1. File Programming
The file operation API of Linux involves creating, opening, reading, writing, and closing files.
Create
Int creat (const char * filename, mode_t mode );
The mode parameter specifies the access permission for the new file. It determines the final permission of the file (Mode & umask) together with umask. umask indicates some access permissions that need to be removed during file creation. Umask can be changed by calling umask:
Int umask (INT newmask );
This call sets umask as newmask and returns the old umask, which only affects read, write, and execution permissions.
Open
Int open (const char * pathname, int flags );
Int open (const char * pathname, int flags, mode_t mode );
Read/write
After the file is opened, we can read and write the file. in Linux, the Read and Write Functions are called by the system:
Int read (int fd, const void * Buf, size_t length );
Int write (int fd, const void * Buf, size_t length );
The parameter Buf is the pointer to the buffer, and the length is the buffer size (in bytes ). The read () function reads length bytes from the file specified by the file descriptor FD to the buffer zone pointed to by the BUF. The returned value is the actual number of bytes read. Function write writes the Length byte from the buffer to which the Buf points to the file pointed by the file descriptor FD. the return value is the actual number of bytes written.
Open marked with o_creat actually implements the file creation function. Therefore, the following function is equivalent to the creat () function:
Int open (pathname, o_creat | o_wronly | o_trunc, mode );
Positioning
For random files, we can read and write at random locations and use the following functions to locate them:
Int lseek (int fd, offset_t offset, int whence );
Lseek () moves the file read/write pointer to the offset byte relative to whence. When the operation is successful, the position of the returned file pointer relative to the file header. The whence parameter can use the following values:
Seek_set: Start of relative file
Seek_cur: Current Position of the relative file read/write pointer
Seek_end: relative to the end of the file
The offset value can be a negative value. For example, the following call can move the file pointer 5 bytes forward from the current position:
Lseek (FD,-5, seek_cur );
Because the return value of the lseek function is the position of the file pointer relative to the file header, the following returned values are the file length:
Lseek (FD, 0, seek_end );
Close
You only need to call close. FD is the file descriptor to be closed:
Int close (int fd );
Next we will compile an application, create a user-readable and writable file "example.txt" in the current directory, write "Hello World" in it, close the file, and open it again, read the content and output it on the screen:
# Include <sys/types. h>
# Include <sys/STAT. h>
# Include <fcntl. h>
# Include <stdio. h>
# Define length 100
Main ()
{
Int FD, Len;
Char STR [length];
FD = open ("hello.txt", o_creat | o_rdwr, s_irusr | s_iwusr);/* Create and open a file */
If (FD)
{
Write (FD, "Hello, software Weekly", strlen ("Hello, software Weekly "));
/* Write the hello, software weekly string */
Close (FD );
}
FD = open ("hello.txt", o_rdwr );
Len = read (FD, STR, length);/* Read File Content */
STR [Len] = '\ 0 ';
Printf ("% s \ n", STR );
Close (FD );
}
2. Process Control/communication programming
Process control mainly involves Process Creation, sleep, and exit. in Linux, the fork, exec, and clone process creation methods are provided, sleep process sleep and exit process exit call. In addition, Linux also provides the system call wait for the parent process to wait for the child process to end.
Fork
For those who have never been familiar with Unix/Linux operating systems, fork is one of the most difficult concepts to understand, because it returns two values at a time when it is executed, which was previously "unknown ". First look at the following program:
Int main ()
{
Int I;
If (Fork () = 0)
{
For (I = 1; I <3; I ++)
Printf ("this is child process \ n ");
}
Else
{
For (I = 1; I <3; I ++)
Printf ("this is parent process \ n ");
}
}
The execution result is:
This is child process
This is child process
This is parent process
This is parent process
Fork means "fork" in English. When a process is running, if fork is used, another process is generated, so the process is "fork. The current process is a parent process, and a child process is generated through fork. For the parent process, the fork function returns the process Number of the subroutine, and for the subroutine, the fork function returns zero, which is the essence of a function that returns twice.
Exec
In Linux, you can use the exec function family, which contains multiple functions (execl, execlp, execle, execv, execve, and execvp) to start a process with a specified path and file name. The features of the exec function family are as follows: Once a process calls the exec function, the program being executed is killed. The system replaces the code segment with a new program (executed by the exec function) and the original data segment and stack segment are also discarded. The new data segment and stack segment are allocated, but the process number is retained. That is to say, the exec execution result is: the system considers that the execution is still the original process, but the program corresponding to the process is replaced.
The fork function can create a sub-process and the current process does not die, if we call the exec function family in the Fork sub-process, we can execute the code of the parent process and start a new specified process. This is good. The combination of fork and exec cleverly solves the problem that the program starts the execution of another program but continues to run. See the following example:
Char Command [max_cmd_len];
Void main ()
{
Int RTN;/* the return value of the sub-process */
While (1)
{
/* Read the command to be executed from the terminal */
Printf ("> ");
Fgets (command, max_cmd_len, stdin );
Command [strlen (command)-1] = 0;
If (Fork () = 0)
{
/* The sub-process executes this command */
Execlp (command, command );
/* If the exec function returns, the command is not executed normally and the error message is printed */
Perror (command );
Exit (errorno );
}
Else
{
/* Parent process. Wait until the child process ends and print the return value of the child process */
Wait (& RTN );
Printf ("child process return % d \ n", RTN );
}
}
}
This function implements a shell function that reads the process name and Parameters entered by the user and starts the corresponding process.
Clone
Clone is a new function provided after linux2.0. It is more powerful than fork (you can think of fork as a part of clone implementation), so that the created sub-process can share the resources of the parent process, to use this function, you must set the clone_actually_works_ OK option during kernel compilation.
The clone function is prototype:
Int clone (INT (* fN) (void *), void * child_stack, int flags, void * Arg );
This function returns the PID of the created process. The flags flag in the function is used to set related options when creating a child process.
Let's look at the following example:
Int variable, FD;
Int do_something (){
Variable = 42;
Close (FD );
_ Exit (0 );
}
Int main (INT argc, char * argv []) {
Void ** child_stack;
Char tempch;
Variable = 9;
FD = open ("test. File", o_rdonly );
Child_stack = (void **) malloc (16384 );
Printf ("the variable was % d \ n", variable );
Clone (do_something, child_stack, clone_vm | clone_files, null );
Sleep (1);/* delay so that the sub-process can close the file and modify the variable */
Printf ("the variable is now % d \ n", variable );
If (read (FD, & tempch, 1) <1 ){
Perror ("file read error ");
Exit (1 );
}
Printf ("We cocould read from the file \ n ");
Return 0;
}
Running output:
The variable is now 42
File Read Error
The output result of the program tells us that the sub-process closes the file and modifies the variable (the clone_vm and clone_files used for calling clone will make the variables and file descriptor tables shared ), the parent process immediately feels like clone.
Sleep
The function call sleep can be used to suspend a process for a specified number of seconds. The prototype of this function is:
Unsigned int sleep (unsigned int seconds );
This function call suspends a process at a specified time. If the specified suspension time is reached, the call returns 0. If the function call is interrupted by a signal, the number of remaining suspension times (the specified time minus the suspended time) are returned ).
Exit
The system calls the exit function to terminate the process. Its function prototype is:
Void _ exit (INT status );
_ Exit will immediately terminate the calling process, and all file descriptors belonging to the process will be disabled. The status parameter is returned to the parent process as the exit status value. You can obtain this value by calling wait in the parent process.
Wait
Wait system calls include:
Pid_t wait (int * status );
Pid_t waitpid (pid_t PID, int * status, int options );
The purpose of wait is to ensure that any sub-process that sends a call will sleep until one of them stops. The waitpid waits for the sub-process specified by the PID parameter to exit.
In Linux, inter-process communication (IPC) includes pipelines, message queues, shared memory, semaphores, and APIs. Socket communication is not proprietary to Linux. Almost all operating systems that provide TCP/IP protocol stacks provide sockets, the programming methods for sockets are almost the same. Pipelines can be divided into famous and unknown pipelines. Nameless pipelines can only be used for communication between relatives' processes, while famous pipelines can be used between non-kinship processes; message Queues are used for communication between processes running on the same machine, similar to pipelines. Shared Memory is usually created by one process, and other processes read and write the memory area. semaphores are a counter, it is used to record the access status of a resource (such as shared memory.
The following is an example of using semaphores. This program creates a keyword for a specific IPC structure and a semaphores, creates an index for this semaphores, modifies the semaphore value pointed to by the index, and finally clears the semaphores:
# Include <stdio. h>
# Include <sys/types. h>
# Include <sys/SEM. h>
# Include <sys/IPC. h>
Void main ()
{
Key_t unique_key;/* defines an IPC keyword */
Int ID;
Struct sembuf lock_it;
Union semun options;
Int I;
Unique_key = ftok (".", 'A');/* generate a keyword. The character 'a' is a random seed */
/* Create a new semaphore Set */
Id = semget (unique_key, 1, ipc_creat | ipc_excl | 0666 );
Printf ("semaphore id = % d \ n", ID );
Options. Val = 1;/* set the variable value */
Semctl (ID, 0, setval, options);/* set the semaphore of index 0 */
/* Print the semaphore value */
I = semctl (ID, 0, getval, 0 );
Printf ("value of semaphore at index 0 is % d \ n", I );
/* Reset the semaphore below */
Lock_it.sem_num = 0;/* specifies the semaphore */
Lock_it.sem_op =-1;/* define operation */
Lock_it.sem_flg = ipc_nowait;/* Operation Method */
If (semop (ID, & lock_it, 1) =-1)
{
Printf ("can not lock semaphore. \ n ");
Exit (1 );
}
I = semctl (ID, 0, getval, 0 );
Printf ("value of semaphore at index 0 is % d \ n", I );
/* Clear semaphores */
Semctl (ID, 0, ipc_rmid, 0 );
}
3. thread control/communication programming
Linux only has the concept of process, and its so-called "Thread" is essentially a process in the kernel. As you know, a process is the unit of resource allocation. multiple threads in the same process share the resources of the process (such as global variables used as shared memory ). In Linux, the so-called "Thread" is just "clone" (clone) The resources of the parent process when it is created. Therefore, the cloned process is "Thread ". The most popular thread mechanism in Linux is linuxthreads, which implements a POSIX 1003.1c "pthread" standard interface.
Communication between threads involves synchronization and mutex. The usage of mutex is as follows:
Pthread_mutex_t mutex;
Pthread_mutex_init (& mutex, null); // initialize mutex variable mutex Based on the default attribute
Pthread_mutex_lock (& mutex); // lock the mutex variable
... // Critical resource
Phtread_mutex_unlock (& mutex); // unlock the mutex variable
Synchronization means that the thread waits for an event. The thread continues to execute only when the waiting event occurs. Otherwise, the thread suspends and discards the processor. When multiple threads collaborate, the interaction tasks must be synchronized under certain conditions. In Linux, C language programming has multiple thread synchronization mechanisms, the most typical of which is conditional variable ). In the header file semaphore. the semaphores defined in H encapsulate mutex and condition variables. The access control mechanism is designed according to the multi-threaded program to control synchronous access to resources, provide more convenient interfaces for program designers. The following producer/consumer problems describe the control and communication of Linux threads:
# Include <stdio. h>
# Include <pthread. h>
# Define buffer_size 16
Struct prodcons
{
Int buffer [buffer_size];
Pthread_mutex_t lock;
Int readpos, writepos;
Pthread_cond_t notempty;
Pthread_cond_t notfull;
};
/* Initialize the Buffer Structure */
Void Init (struct prodcons * B)
{
Pthread_mutex_init (& B-> lock, null );
Pthread_cond_init (& B-> notempty, null );
Pthread_cond_init (& B-> notfull, null );
B-> readpos = 0;
B-> writepos = 0;
}
/* Put the product into the buffer zone. Here is an integer */
Void put (struct prodcons * B, int data)
{
Pthread_mutex_lock (& B-> lock );
/* Wait for the buffer to be below */
If (B-> writepos + 1) % buffer_size = B-> readpos)
{
Pthread_cond_wait (& B-> notfull, & B-> lock );
}
/* Write data and move the pointer */
B-> buffer [B-> writepos] = data;
B-> writepos ++;
If (B-> writepos> = buffer_size)
B-> writepos = 0;
/* Set non-empty condition variables in the buffer zone */
Pthread_cond_signal (& B-> notempty );
Pthread_mutex_unlock (& B-> lock );
}
/* Retrieve the integer from the buffer */
Int get (struct prodcons * B)
{
Int data;
Pthread_mutex_lock (& B-> lock );
/* Wait for the buffer to be non-empty */
If (B-> writepos = B-> readpos)
{
Pthread_cond_wait (& B-> notempty, & B-> lock );
}
/* Read data, move the read pointer */
Data = B-> buffer [B-> readpos];
B-> readpos ++;
If (B-> readpos> = buffer_size)
B-> readpos = 0;
/* Set the condition variable with the buffer not full */
Pthread_cond_signal (& B-> notfull );
Pthread_mutex_unlock (& B-> lock );
Return data;
}
/* Test: The producer thread sends an integer from 1 to 10000 to the buffer, and the Consumer line
Obtain the integer from the buffer, both of which print the information */
# Define over (-1)
Struct prodcons buffer;
Void * producer (void * Data)
{
Int N;
For (n = 0; n <10000; n ++)
{
Printf ("% d ---> \ n", N );
Put (& buffer, N );
} Put (& buffer, over );
Return NULL;
}
Void * Consumer (void * Data)
{
Int D;
While (1)
{
D = get (& buffer );
If (D = over)
Break;
Printf ("---> % d \ n", d );
}
Return NULL;
}
Int main (void)
{
Pthread_t th_a, th_ B;
Void * retval;
Init (& buffer );
/* Create producer and consumer threads */
Pthread_create (& th_a, null, producer, 0 );
Pthread_create (& th_ B, null, consumer, 0 );
/* Wait for two threads to end */
Pthread_join (th_a, & retval );
Pthread_join (th_ B, & retval );
Return 0;
}
4. Summary
This chapter provides programming examples of files, process control and communication, and thread control and communication on the Linux platform. So far, a complete introduction to the embedded Linux series involving hardware principles, bootloader, operating system and file system transplantation, driver development, and application programming is all over.