运用实例的编写实践上现已不归于Linux操作体系移植的领域,可是为了确保本系列文章的完好性,这儿供给一系列针对嵌入式Linux开发运用程序的实例。
编写Linux运用程序要用到如下东西:
(1)编译器:GCC
GCC是Linux渠道下最重要的开发东西,它是GNU的C和C++编译器,其根本用法为:gcc [options] [filenames]。
咱们应该运用arm-linux-gcc。
(2)调试器:GDB
gdb是一个用来调试C和C++程序的强力调试器,咱们能经过它进行一系列调试作业,包含设置断点、观查变量、单步等。
咱们应该运用arm-linux-gdb。
(3)Make
GNU Make的首要作业是读进一个文本文件,称为makefile。这个文件记载了哪些文件由哪些文件产生,用什么指令来产生。Make依托此makefile中的信息查看磁盘上的文件,假如意图文件的创立或修正时刻比它的一个依托文件旧的话,make就履行相应的指令,以便更新意图文件。
Makefile中的编译规则要相应地运用arm-linux-版别。
(4)代码修正
能够运用传统的vi修正器,但最好选用emacs软件,它具有语法高亮、版别操控等顺便功用。
在宿主机上用上述东西完结运用程序的开发后,能够经过如下途径将程序下载到方针板上运转:
(1)经过串口通讯协议rz将程序下载到方针板的文件体系中(感谢Linux供给了rz这样的一个指令);
(2)经过ftp通讯协议从宿主机上的ftp目录里将程序下载到方针板的文件体系中;
(3)将程序拷入U盘,在方针机上mount U盘,运转U盘中的程序;
(4)假如方针机Linux运用NFS文件体系,则能够直接将程序拷入到宿主机相应的目录内,在方针机Linux中能够直接运用。
1.文件编程
Linux的文件操作API触及到创立、翻开、读写和封闭文件。
创立
int creat(const char *filename, mode_t mode);
参数mode指定新建文件的存取权限,它同umask一同决议文件的终究权限(mode&umask),其间umask代表了文件在创立时需求去掉的一些存取权限。umask可经过体系调用umask()来改动:
int umask(int newmask);
该调用将umask设置为newmask,然后回来旧的umask,它只影响读、写和履行权限。
翻开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
读写
在文件翻开今后,咱们才可对文件进行读写了,Linux中供给文件读写的体系调用是read、write函数:
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其间参数buf为指向缓冲区的指针,length为缓冲区的巨细(以字节为单位)。函数read()完结从文件描述符fd所指定的文件中读取length个字节到buf所指向的缓冲区中,回来值为实践读取的字节数。函数write完结将把length个字节从buf指向的缓冲区中写到文件描述符fd所指向的文件中,回来值为实践写入的字节数。
以O_CREAT为标志的open实践上完结了文件创立的功用,因而,下面的函数同等creat()函数:
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
定位
关于随机文件,咱们能够随机的指定方位读写,运用如下函数进行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()将文件读写指针相对whence移动offset个字节。操作成功时,回来文件指针相关于文件头的方位。参数whence可运用下述值:
SEEK_SET:相对文件最初
SEEK_CUR:相对文件读写指针的当时方位
SEEK_END:相对文件结尾
offset可取负值,例如下述调用可将文件指针相对当时方位向前移动5个字节:
lseek(fd, -5, SEEK_CUR);
由于lseek函数的回来值为文件指针相关于文件头的方位,因而下列调用的回来值便是文件的长度:
lseek(fd, 0, SEEK_END);
封闭
只需调用close就能够了,其间fd是咱们要封闭的文件描述符:
int close(int fd);
下面咱们来编写一个运用程序,在当时目录下创立用户可读写文件“example.txt”,在其间写入“Hello World”,封闭文件,再次翻开它,读取其间的内容并输出在屏幕上:
#include
#include
#include
#include
#define LENGTH 100
main()
{
int fd, len;
char str[LENGTH];
fd = open(“hello.txt”, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 创立并翻开文件 */
if (fd)
{
write(fd, “Hello, Software Weekly”, strlen(“Hello, software weekly”)); /* 写入Hello, software weekly字符串 */
close(fd);
}
fd = open(“hello.txt”, O_RDWR);
len = read(fd, str, LENGTH); /* 读取文件内容 */
str[len] = ;
printf(“%s\n”, str);
close(fd);
}
2.进程操控/通讯编程
进程操控中首要触及到进程的创立、睡觉和退出等,在Linux中首要供给了fork、exec、clone的进程创立办法,sleep的进程睡觉和exit的进程退出调用,别的Linux还供给了父进程等候子进程完毕的体系调用wait。
fork
关于没有触摸过Unix/Linux操作体系的人来说,fork是最难了解的概念之一,由于它履行一次却回来两个值,曾经“闻所未闻”。先看下面的程序:
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”);
}
}
履行成果为:
This is child process
This is child process
This is parent process
This is parent process
fork在英文中是“分叉”的意思,一个进程在运转中,假如运用了fork,就产生了另一个进程,所以进程就“分叉”了。当时进程为父进程,经过fork()会产生一个子进程。关于父进程,fork函数回来子程序的进程号而关于子程序,fork函数则回来零,这便是一个函数回来两次的实质。
exec
在Linux中可运用exec函数族,包含多个函数(execl、execlp、execle、execv、execve和execvp),被用于发动一个指定途径和文件名的进程。exec函数族的特色体现在:某进程一旦调用了exec类函数,正在履行的程序就被干掉了,体系把代码段替换成新的程序(由exec类函数履行)的代码,而且原有的数据段和仓库段也被抛弃,新的数据段与仓库段被分配,可是进程号却被保存。也便是说,exec履行的成果为:体系以为正在履行的仍是原先的进程,可是进程对应的程序被替换了。
fork函数能够创立一个子进程而当时进程不死,假如咱们在fork的子进程中调用exec函数族就能够完结既让父进程的代码履行又发动一个新的指定进程,这很好。fork和exec的调配奇妙地处理了程序发动另一程序的履行但自己仍持续运转的问题,请看下面的比如:
char command[MAX_CMD_LEN];
void main()
{
int rtn; /* 子进程的回来数值 */
while (1)
{
/* 从终端读取要履行的指令 */
printf(“>”);
fgets(command, MAX_CMD_LEN, stdin);
command[strlen(command) – 1] = 0;
if (fork() == 0)
{
/* 子进程履行此指令 */
execlp(command, command);
/* 假如exec函数回来,标明没有正常履行指令,打印错误信息*/
perror(command);
exit(errorno);
}
else
{
/* 父进程,等候子进程完毕,并打印子进程的回来值 */
wait(&rtn);
printf(” child process return %d\n”, rtn);
}
}
}
这个函数完结了一个shell的功用,它读取用户输入的进程名和参数,并发动对应的进程。
clone
clone是Linux2.0今后才具有的新功用,它较fork更强(能够为fork是clone要完结的一部分),能够使得创立的子进程同享父进程的资源,而且要运用此函数有必要在编译内核时设置clone_actually_works_ok选项。
clone函数的原型为:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
此函数回来创立进程的PID,函数中的flags标志用于设置创立子进程时的相关选项。
来看下面的比如:
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); /* 延时以便子进程完结封闭文件操作、修正变量 */
printf(“The variable is now %d\n”, variable);
if (read(fd, &tempch, 1) < 1) {
perror(“File Read Error”);
exit(1);
}
printf(“We could read from the file\n”);
return 0;
}
运转输出:
The variable is now 42
File Read Error
程序的输出成果告知咱们,子进程将文件封闭并将变量修正(调用clone时用到的CLONE_VM、CLONE_FILES标志将使得变量和文件描述符表被同享),父进程随即就感觉到了,这便是clone的特色。
sleep
函数调用sleep能够用来使进程挂起指定的秒数,该函数的原型为:
unsigned int sleep(unsigned int seconds);
该函数调用使得进程挂起一个指定的时刻,假如指定挂起的时刻到了,该调用回来0;假如该函数调用被信号所打断,则回来剩下挂起的时刻数(指定的时刻减去现已挂起的时刻)。
exit
体系调用exit的功用是停止本进程,其函数原型为:
void _exit(int status);
_exit会当即停止宣布调用的进程,一切归于该进程的文件描述符都封闭。参数status作为退出的状态值回来父进程,在父进程中经过体系调用wait可获得此值。
wait
wait体系调用包含:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait的作用为宣布调用的进程只需有子进程,就睡觉到它们中的一个停止停止; waitpid等候由参数pid指定的子进程退出。
Linux的进程间通讯(IPC,InterProcess Communication)通讯办法有管道、音讯行列、同享内存、信号量、套接口等。套接字通讯并不为Linux所专有,在一切供给了TCP/IP协议栈的操作体系中简直都供给了socket,而一切这样操作体系,对套接字的编程办法简直是彻底相同的。管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通讯,而有名管道则可用于无亲属关系的进程之间;音讯行列用于运转于同一台机器上的进程间通讯,与管道类似;同享内存一般由一个进程创立,其他进程对这块内存区进行读写;信号量是一个计数器,它用来记载对某个资源(如同享内存)的存取情况。
下面是一个运用信号量的比如,该程序创立一个特定的IPC结构的关键字和一个信号量,树立此信号量的索引,修正索引指向的信号量的值,最终铲除信号量:
#include
#include
#include
#include
void main()
{
key_t unique_key; /* 界说一个IPC关键字*/
int id;
struct sembuf lock_it;
union semun options;
int i;
unique_key = ftok(“.”, a); /* 生成关键字,字符a是一个随机种子*/
/* 创立一个新的信号量调集*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
printf(“semaphore id=%d\n”, id);
options.val = 1; /*设置变量值*/
semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/
/*打印出信号量的值*/
i = semctl(id, 0, GETVAL, 0);
printf(“value of semaphore at index 0 is %d\n”, i);
/*下面从头设置信号量*/
lock_it.sem_num = 0; /*设置哪个信号量*/
lock_it.sem_op = – 1; /*界说操作*/
lock_it.sem_flg = IPC_NOWAIT; /*操作方法*/
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);
/*铲除信号量*/
semctl(id, 0, IPC_RMID, 0);
}
3.线程操控/通讯编程
Linux自身只要进程的概念,而其所谓的“线程”实质上在内核里仍然是进程。我们知道,进程是资源分配的单位,同一进程中的多个线程同享该进程的资源(如作为同享内存的全局变量)。Linux中所谓的“线程”仅仅在被创立的时分“克隆”(clone)了父进程的资源,因而,clone出来的进程表现为“线程”。Linux中最盛行的线程机制为LinuxThreads,它完结了一种Posix 1003.1c “pthread”标准接口。
线程之间的通讯触及同步和互斥,互斥体的用法为:
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的特点初始化互斥体变量mutex
pthread_mutex_lock(&mutex); // 给互斥体变量加锁
… //临界资源
phtread_mutex_unlock(&mutex); // 给互斥体变量解锁
同步便是线程等候某个事情的产生。只要当等候的事情产生线程才持续履行,不然线程挂起并抛弃处理器。当多个线程协作时,相互作用的使命有必要在必定的条件下同步。Linux下的C言语编程有多种线程同步机制,最典型的是条件变量(condition variable)。而在头文件semaphore.h 中界说的信号量则完结了互斥体和条件变量的封装,依照多线程程序设计中拜访操控机制,操控对资源的同步拜访,供给程序设计人员更便利的调用接口。下面的生产者/顾客问题说明晰Linux线程的操控和通讯:
#include
#include
#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;
};
/* 初始化缓冲区结构 */
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;
}
/* 将产品放入缓冲区,这儿是存入一个整数*/
void put(struct prodcons *b, int data)
{
pthread_mutex_lock(&b->lock);
/* 等候缓冲区未满*/
if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
{
pthread_cond_wait(&b->notfull, &b->lock);
}
/* 写数据,并移动指针 */
b->buffer[b->writepos] = data;
b->writepos++;
if (b->writepos > = BUFFER_SIZE)
b->writepos = 0;
/* 设置缓冲区非空的条件变量*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/* 从缓冲区中取出整数*/
int get(struct prodcons *b)
{
int data;
pthread_mutex_lock(&b->lock);
/* 等候缓冲区非空*/
if (b->writepos == b->readpos)
{
pthread_cond_wait(&b->notempty, &b->lock);
}
/* 读数据,移动读指针*/
data = b->buffer[b->readpos];
b->readpos++;
if (b->readpos > = BUFFER_SIZE)
b->readpos = 0;
/* 设置缓冲区未满的条件变量*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
return data;
}
/* 测验:生产者线程将1 到10000 的整数送入缓冲区,顾客线
程从缓冲区中获取整数,两者都打印信息*/
#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);
/* 创立生产者和顾客线程*/
pthread_create(&th_a, NULL, producer, 0);
pthread_create(&th_b, NULL, consumer, 0);
/* 等候两个线程完毕*/
pthread_join(th_a, &retval);
pthread_join(th_b, &retval);
return 0;
}
4.小结
本章首要给出了Linux渠道下文件、进程操控与通讯、线程操控与通讯的编程实例。至此,一个完好的,触及硬件原理、Bootloader、操作体系及文件体系移植、驱动程序开发及运用程序编写的嵌入式Linux系列解说就悉数完毕了。