The writing of application examples actually does not fall within the scope of Linux operating system porting, but in order to ensure the completeness of this series of articles, a series of examples of developing applications for embedded Linux are provided here.
The following tools are used to write Linux applications:
(1) Compiler: GCC
GCC is the most important development tool under the Linux platform. It is the GNU C and C++ compiler. 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, observing variables, single-stepping, etc.
We should use arm-linux-gdb.
(3) Make
The main work of GNU Make is to read a text file, called makefile. This file records which files are generated by which files and what commands are used to generate them. Make relies on the information in this makefile to check the files on the disk. If the creation or modification time of the target file is older than one of its dependent files, make executes the corresponding command to update the target file.
The compilation rules in the Makefile should use the arm-linux- version accordingly.
(4) Code editing
You can use the traditional vi editor, but it is better to use emacs software, which has additional features such as syntax highlighting and version control.
After completing the application development on the host machine using the above tools, you can download the program to the target board and run it in the following ways:
(1) Download the program to the target board's file system through the serial communication protocol rz (thanks to Linux for providing a command like rz);
(2) Download the program from the FTP directory on the host machine to the file system of the target board through the FTP communication protocol;
(3) Copy the program to 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 Linux uses the NFS file system, you can directly copy the program into the corresponding directory of the host machine and use it directly in the target Linux.
1. File programming
The Linux file operation API involves creating, opening, reading, writing, and closing files.
create
intcreat(constchar*filename,mode_tmode);
The parameter mode specifies the access permissions of the newly created file. It and umask together determine the final permissions of the file (mode & umask), where umask represents some access permissions that need to be removed when the file is created. The umask can be changed by the system call umask():
intumask(intnewmask);
This call sets the umask to newmask and then returns the old umask, which affects only read, write, and execute permissions.
Open
intopen(constchar*pathname,intflags);
intopen(constchar*pathname,intflags,mode_tmode);
Read and Write
After the file is opened, we can read and write the file. The system calls that provide file reading and writing in Linux are read and write functions:
intread(intfd,constvoid*buf,size_tlength);
intwrite(intfd,constvoid*buf,size_tlength);
The parameter buf is a pointer to the buffer, and length is the size of the buffer (in bytes). The read() function reads length bytes from the file specified by the file descriptor fd into the buffer pointed to by buf, and the return value is the number of bytes actually read. The write function writes length bytes from the buffer pointed to by buf to the file pointed to by the file descriptor fd, and the return value is the number of bytes actually written.
The open with O_CREAT as the flag actually implements the file creation function, so the following function is equivalent to the creat() function:
intopen(pathname,O_CREAT|O_WRONLY|O_TRUNC,mode);
position
For random files, we can read and write at random specified locations and use the following functions for positioning:
intlseek(intfd,offset_toffset,intwhence);
lseek() moves the file read/write pointer by offset bytes relative to whence. If the operation succeeds, it returns the position of the file pointer relative to the file header. The whence parameter can use the following values:
SEEK_SET: relative to the beginning of the file
SEEK_CUR: Current position of the relative file read/write pointer
SEEK_END: relative to the end of the file
Offset can take negative values. For example, the following call moves the file pointer 5 bytes forward relative to the current position:
lseek(fd,-5,SEEK_CUR);
Since the return value of the lseek function is the position of the file pointer relative to the file header, the return value of the following call is the length of the file:
lseek(fd,0,SEEK_END);
closure
Just call close, where fd is the file descriptor we want to close:
intclose(intfd);
Let's write an application that creates a user-readable and writable file "example.txt" in the current directory, writes "HelloWorld" in it, closes the file, opens it again, reads the content and outputs it on the screen:
#include
#include
#include
#include
#defineLENGTH100
main()
{
intfd,len;
charstr[LENGTH];
fd=open("hello.txt",O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);/*Create and open the file*/
if(fd)
{
write(fd,"Hello,SoftwareWeekly",strlen("Hello,softwareweekly"));
/*Write Hello, softwareweekly string*/
close(fd);
}
fd=open("hello.txt",O_RDWR);
len=read(fd,str,LENGTH);/*read file contents*/
str[len]='';
printf("%sn",str);
close(fd);
}
2. Process control/communication programming Process control mainly involves process creation, sleep and exit. Linux mainly provides fork, exec, clone process creation methods, sleep process sleep and exit process exit calls. 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 exposed to Unix/Linux operating systems, fork is one of the most difficult concepts to understand, because it returns two values after executing once, which is "unheard of" before. Let's look at the following program:
intmain()
{
inti;
if(fork()==0)
{
for(i=1;i<3;i++)
printf("Thisischildprocessn");
}
else
{
for(i=1;i<3;i++)
printf("Thisisparentprocessn");
}
}[page]
The execution results are:
This is child process
This is child process
Thisisparentprocess
Thisisparentprocess
Fork means "fork" in English. If a process is running and fork is used, another process will be generated, so the process is "forked". The current process is the parent process, and a child process will be generated through fork(). For the parent process, the fork function returns the process number of the child program, and for the child program, the fork function returns zero, which is the essence of a function returning twice.
exec
In Linux, the exec function family can be used, which includes multiple functions (execl, execlp, execle, execv, execve, and execvp), and is used to start a process with a specified path and file name. The characteristics of the exec function family are as follows: once a process calls an exec-type function, the program being executed is killed, the system replaces the code segment with the code of the new program (executed by the exec-type function), and the original data segment and stack segment are also discarded. New data segments and stack segments are allocated, but the process number is retained. In other words, the result of exec execution is: the system believes that the original process is still being executed, but the program corresponding to the process has been replaced.
The fork function can create a child process while the current process does not die. If we call the exec function family in the forked child process, we can implement both the execution of the parent process's code and the start of a new specified process, which is great. The combination of fork and exec cleverly solves the problem of a program starting another program's execution while continuing to run itself. See the following example:
charcommand[MAX_CMD_LEN];
void main()
{
intrtn;/*return value of the child 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 child process executes this command*/
execlp(command,command);
/*If the exec function returns, it indicates that the command was not executed normally and prints an error message*/
perror(command);
exit(errorno);
}
else
{
/* Parent process, wait for the child process to end, and print the return value of the child process */
wait(&rtn);
printf("childprocessreturn%dn",rtn);
}
}
}
This function implements the functionality of a shell, which reads the process name and parameters entered by the user and starts the corresponding process.
clone
Clone is a new function available after Linux 2.0. It is more powerful than fork (fork can be considered as a part of clone). It allows the created child process to share the resources of the parent process. To use this function, you must set the clone_actually_works_ok option when compiling the kernel.
The prototype of the clone function is:
intclone(int(*fn)(void*),void*child_stack,intflags,void*arg);
This function returns the PID of the created process. The flags flag in the function is used to set relevant options when creating a child process.
Let’s look at the following example:
intvariable,fd;
int do_something(){
variable=42;
close(fd);
_exit(0);
}
intmain(intargc,char*argv[]){
void **child_stack;
chartempch;
variable=9;
fd=open("test.file",O_RDONLY);
child_stack=(void**)malloc(16384);
printf("Thevariablewas%dn",variable);
clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);
sleep(1);/*Delay for the child process to complete closing the file and modifying the variable*/
printf("Thevariableisnow%dn",variable);
if(read(fd,&tempch,1)<1){
perror("FileReadError");
exit(1);
}
printf("Wecouldreadfromthefilen");
return0;
}
Running output:
The variable is now 42
FileReadError
The output of the program tells us that the child process closes the file and modifies the variable (the CLONE_VM and CLONE_FILES flags used when calling clone will make the variables and file descriptor table shared), and the parent process immediately feels it. This is the characteristic of clone.
sleep
The function call sleep can be used to suspend the process for a specified number of seconds. The prototype of the function is:
unsignedintsleep(unsignedintseconds);
This function call suspends the process for a specified time. If the specified suspension time is reached, the call returns 0; if the function call is interrupted by a signal, the remaining suspension time (the specified time minus the already suspended time) is returned.
exit
The function of the system call exit is to terminate the process. Its function prototype is:
void_exit(intstatus);
_exit will immediately terminate the calling process, and all file descriptors belonging to the process will be closed. The parameter status is returned to the parent process as the exit status value, which can be obtained through the system call wait in the parent process.
wait
The wait system calls include:
pid_twait(int*status);
pid_twaitpid(pid_tpid,int*status,intoptions);
The function of wait is to make the calling process sleep until one of its child processes terminates as long as it has any child processes; waitpid waits for the child process specified by the parameter pid to exit.
Linux's interprocess communication (IPC) communication methods include pipes, message queues, shared memory, semaphores, sockets, etc. Socket communication is not exclusive to Linux. Almost all operating systems that provide TCP/IP protocol stacks provide sockets, and all such operating systems have almost the same programming methods for sockets. Pipes are divided into named pipes and unnamed pipes. Unnamed pipes can only be used for communication between related processes, while named pipes can be used between unrelated processes; message queues are used for communication between processes running on the same machine, similar to pipes; shared memory is usually created by one process, and other processes read and write this memory area; a semaphore is a counter that is used to record the access status of a resource (such as shared memory).
The following is an example of using semaphores. The program creates a keyword and a semaphore for a specific IPC structure, creates an index for the semaphore, modifies the value of the semaphore pointed to by the index, and finally clears the semaphore:
#include
#include
#include
#include
[page]
void main()
{
key_tunique_key;/*define an IPC keyword*/
intid;
structsembuflock_it;
unionsemunoptions;
inti;
unique_key=ftok(".",'a');/*Generate a keyword, character 'a' is a random seed*/
/*Create a new semaphore set*/
id=semget(unique_key,1,IPC_CREAT|IPC_EXCL|0666);
printf("semaphoreid=%dn",id);
options.val=1;/*Set variable value*/
semctl(id,0,SETVAL,options);/*Set the semaphore of index 0*/
/*Print out the value of the semaphore*/
i=semctl(id,0,GETVAL,0);
printf("valueofsemaphoreatindex0is%dn",i);
/* Reset the semaphore below */
lock_it.sem_num=0;/*Which semaphore to set*/
lock_it.sem_op=-1;/*define operation*/
lock_it.sem_flg=IPC_NOWAIT;/*Operation mode*/
if(semop(id,&lock_it,1)==-1)
{
printf("cannotlocksemaphore.n");
exit(1);
}
i=semctl(id,0,GETVAL,0);
printf("valueofsemaphoreatindex0is%dn",i);
/* Clear the semaphore */
semctl(id,0,IPC_RMID,0);
}
2. Process control/communication programming Process control mainly involves process creation, sleep and exit. Linux mainly provides fork, exec, clone process creation methods, sleep process sleep and exit process exit calls. 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 exposed to Unix/Linux operating systems, fork is one of the most difficult concepts to understand, because it returns two values after executing once, which is "unheard of" before. Let's look at the following program:
intmain()
{
inti;
if(fork()==0)
{
for(i=1;i<3;i++)
printf("Thisischildprocessn");
}
else
{
for(i=1;i<3;i++)
printf("Thisisparentprocessn");
}
}
The execution results are:
This is child process
This is child process
Thisisparentprocess
Thisisparentprocess
Fork means "fork" in English. If a process is running and fork is used, another process will be generated, so the process is "forked". The current process is the parent process, and a child process will be generated through fork(). For the parent process, the fork function returns the process number of the child program, and for the child program, the fork function returns zero, which is the essence of a function returning twice.
exec
In Linux, the exec function family can be used, which includes multiple functions (execl, execlp, execle, execv, execve, and execvp), and is used to start a process with a specified path and file name. The characteristics of the exec function family are as follows: once a process calls an exec-type function, the program being executed is killed, the system replaces the code segment with the code of the new program (executed by the exec-type function), and the original data segment and stack segment are also discarded. New data segments and stack segments are allocated, but the process number is retained. In other words, the result of exec execution is: the system believes that the original process is still being executed, but the program corresponding to the process has been replaced.
The fork function can create a child process while the current process does not die. If we call the exec function family in the forked child process, we can implement both the execution of the parent process's code and the start of a new specified process, which is great. The combination of fork and exec cleverly solves the problem of a program starting another program's execution while continuing to run itself. See the following example:
charcommand[MAX_CMD_LEN];
void main()
{
intrtn;/*return value of the child 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 child process executes this command*/
execlp(command,command);
/*If the exec function returns, it indicates that the command was not executed normally and prints an error message*/
perror(command);
exit(errorno);
}
else
{
/* Parent process, wait for the child process to end, and print the return value of the child process */
wait(&rtn);
printf("childprocessreturn%dn",rtn);
}
}
}
This function implements the functionality of a shell, which reads the process name and parameters entered by the user and starts the corresponding process.
clone
Clone is a new function available after Linux 2.0. It is more powerful than fork (fork can be considered as a part of clone). It allows the created child process to share the resources of the parent process. To use this function, you must set the clone_actually_works_ok option when compiling the kernel.
The prototype of the clone function is:
intclone(int(*fn)(void*),void*child_stack,intflags,void*arg);
This function returns the PID of the created process. The flags flag in the function is used to set relevant options when creating a child process.
Let’s look at the following example:
intvariable,fd;
int do_something(){
variable=42;
close(fd);
_exit(0);
}
intmain(intargc,char*argv[]){
void **child_stack;
chartempch;
variable=9;
fd=open("test.file",O_RDONLY);
child_stack=(void**)malloc(16384);
printf("Thevariablewas%dn",variable);
clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);
sleep(1);/*Delay for the child process to complete closing the file and modifying the variable*/
printf("Thevariableisnow%dn",variable);
if(read(fd,&tempch,1)<1){
perror("FileReadError");
exit(1);
}
printf("Wecouldreadfromthefilen");
return0;
}
Running output:
The variable is now 42
FileReadError
The output of the program tells us that the child process closes the file and modifies the variable (the CLONE_VM and CLONE_FILES flags used when calling clone will make the variables and file descriptor table shared), and the parent process immediately feels it. This is the characteristic of clone.
[page]
sleep
The function call sleep can be used to suspend the process for a specified number of seconds. The prototype of the function is:
unsignedintsleep(unsignedintseconds);
This function call suspends the process for a specified time. If the specified suspension time is reached, the call returns 0; if the function call is interrupted by a signal, the remaining suspension time (the specified time minus the already suspended time) is returned.
exit
The function of the system call exit is to terminate the process. Its function prototype is:
void_exit(intstatus);
_exit will immediately terminate the calling process, and all file descriptors belonging to the process will be closed. The parameter status is returned to the parent process as the exit status value, which can be obtained through the system call wait in the parent process.
wait
The wait system calls include:
pid_twait(int*status);
pid_twaitpid(pid_tpid,int*status,intoptions);
The function of wait is to make the calling process sleep until one of its child processes terminates as long as it has any child processes; waitpid waits for the child process specified by the parameter pid to exit.
Linux's interprocess communication (IPC) communication methods include pipes, message queues, shared memory, semaphores, sockets, etc. Socket communication is not exclusive to Linux. Almost all operating systems that provide TCP/IP protocol stacks provide sockets, and all such operating systems have almost the same programming methods for sockets. Pipes are divided into named pipes and unnamed pipes. Unnamed pipes can only be used for communication between related processes, while named pipes can be used between unrelated processes; message queues are used for communication between processes running on the same machine, similar to pipes; shared memory is usually created by one process, and other processes read and write this memory area; a semaphore is a counter that is used to record the access status of a resource (such as shared memory).
The following is an example of using semaphores. The program creates a keyword and a semaphore for a specific IPC structure, creates an index for the semaphore, modifies the value of the semaphore pointed to by the index, and finally clears the semaphore:
#include
#include
#include
#include
void main()
{
key_tunique_key;/*define an IPC keyword*/
intid;
structsembuflock_it;
unionsemunoptions;
inti;
unique_key=ftok(".",'a');/*Generate a keyword, character 'a' is a random seed*/
/*Create a new semaphore set*/
id=semget(unique_key,1,IPC_CREAT|IPC_EXCL|0666);
printf("semaphoreid=%dn",id);
options.val=1;/*Set variable value*/
semctl(id,0,SETVAL,options);/*Set the semaphore of index 0*/
/*Print out the value of the semaphore*/
i=semctl(id,0,GETVAL,0);
printf("valueofsemaphoreatindex0is%dn",i);
/* Reset the semaphore below */
lock_it.sem_num=0;/*Which semaphore to set*/
lock_it.sem_op=-1;/*define operation*/
lock_it.sem_flg=IPC_NOWAIT;/*Operation mode*/
if(semop(id,&lock_it,1)==-1)
{
printf("cannotlocksemaphore.n");
exit(1);
}
i=semctl(id,0,GETVAL,0);
printf("valueofsemaphoreatindex0is%dn",i);
/* Clear the semaphore */
semctl(id,0,IPC_RMID,0);
}
3. Thread control/communication programming Linux itself only has the concept of process, and its so-called "thread" is essentially still a process in the kernel. As we all know, a process is a unit of resource allocation, and multiple threads in the same process share the resources of the process (such as global variables as shared memory). The so-called "thread" in Linux is just "cloned" (cloned) the resources of the parent process when it is created, so the cloned process is manifested as a "thread". The most popular thread mechanism in Linux is LinuxThreads, which implements a Posix1003.1c "pthread" standard interface.
Communication between threads involves synchronization and mutual exclusion. The usage of mutex is:
pthread_mutex_tmutex;
pthread_mutex_init(&mutex,NULL); //Initialize the mutex variable mutex according to the default attributes
pthread_mutex_lock(&mutex);//Lock the mutex variable
...//Critical resources
phtread_mutex_unlock(&mutex);//Unlock the mutex variable
Synchronization means that a thread waits for an event to occur. The thread will continue to execute only when the event it is waiting for occurs, otherwise the thread will suspend and give up the processor. When multiple threads collaborate, the interacting tasks must be synchronized under certain conditions. There are many thread synchronization mechanisms in C language programming under Linux, the most typical of which is the condition variable. The semaphore defined in the header file semaphore.h completes the encapsulation of mutexes and condition variables, and controls the synchronous access to resources according to the access control mechanism in multi-threaded programming, providing programmers with a more convenient calling interface. The following producer/consumer problem illustrates the control and communication of Linux threads:
#include
#include
#defineBUFFER_SIZE16
structprodcons
{
intbuffer[BUFFER_SIZE];
pthread_mutex_tlock;
intreadpos,writepos;
pthread_cond_tnotempty;
pthread_cond_tnotfull;
};
/* Initialize 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, here is to store an integer*/
voidput(structprodcons*b,intdata)
{
pthread_mutex_lock(&b->lock);
/*Wait until the buffer is not full*/
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;
[page]
b->writepos++;
if(b->writepos>=BUFFER_SIZE)
b->writepos=0;
/*Set the condition variable that the buffer is not empty*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/*Get integer from buffer*/
int get(structprodcons*b)
{
intdata;
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 read pointer*/
data=b->buffer[b->readpos];
b->readpos++;
if(b->readpos>=BUFFER_SIZE)
b->readpos=0;
/*Set the condition variable if the buffer is not full*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
returndata;
}
/* Test: The producer thread sends integers from 1 to 10000 into the buffer, and the consumer thread
The program gets the integer from the buffer, both print information */
#defineOVER(-1)
structprodconsbuffer;
void*producer(void*data)
{
intn;
for(n=0;n<10000;n++)
{
printf("%d--->n",n);
put(&buffer,n);
}put(&buffer,OVER);
returnNULL;
}
void*consumer(void*data)
{
intd;
while(1)
{
d=get(&buffer);
if(d==OVER)
break;
printf("--->%dn",d);
}
returnNULL;
}
intmain(void)
{
pthread_tth_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 both threads to finish*/
pthread_join(th_a,&retval);
pthread_join(th_b,&retval);
return0;
}
4. Summary
This chapter mainly gives programming examples of files, process control and communication, thread control and communication under the Linux platform. So far, a complete series of explanations on embedded Linux involving hardware principles, bootloader, operating system and file system transplantation, driver development and application programming has come to an end.
Previous article:Electronic throttle control based on ARM-Linux platform
Next article:ARM interrupt vector design scheme
Recommended ReadingLatest update time:2024-11-17 00:29
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- Talking about software architecture from the perspective of history - object-oriented programming (Part 3)
- DCDC input end inrush current problem
- Non-blocking assignment order problem
- [Raspberry Pi 4B Review] + Real-time status monitoring website server construction
- Smart Home Series Articles | Battle for Control: Who is the control center of the smart home system?
- EEWORLD University Hall----Live Replay: MCU programming is no longer difficult, use MPLAB? Code Configurator (MCC) to achieve rapid development
- Understanding of the working principle of single chip microcomputer
- Talk about the basic structure of MSP430 program library
- Why can't the mobile and computer versions be horizontal?
- TMS320F28335——Download program to flash