PX4学习之uorb

2023-05-16

PX4学习之uorb

1线程

1.1优点

在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

1.2简单多线程编程实现

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork()用法。

//pthread.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void thread(void)
{
    int i;
    for(i=0;i<3;i++)
        printf("This is a pthread.\n");
}

int main(void)
{
    pthread_t id;
    int i,ret;
    ret=pthread_create(&id,NULL,(void *) thread,NULL);
    if(ret!=0){
        printf ("Create pthread error!\n");
        exit (1);
    }
    for(i=0;i<3;i++)
        printf("This is the main process.\n");
    pthread_join(id,NULL);
    return (0);
}

编译c文件

gcc pthread.c -lpthread -o pthread

运行可执行文件./pthread
输出结果:

This is the main process.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
This is a pthread.

pthread_t

pthread_t是一个线程的标识符,它被用来定义线程的ID;pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:

typedef unsigned long int pthread_t

pthread_create

pthread_create是UNIX环境中创建线程的函数包含在头文件phread.h
声明如下:

extern int pthread_create(pthread_t* restrict tidp,const pthread_attr_t* restrict_attr,void* (*start_rtn)(void*),void *restrict arg);

(1)tidp:事先创建好的pthread_t类型的线程的标识符。成功时tidp指向的内存单元被设置为新创建线程的线程ID。
(2)attr:用于定制各种不同的线程属性。通常直接设为NULL。
(3)start_rtn:新创建线程从此函数开始运行。无参数是arg设为NULL即可。
(4)arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。
5、返回值:成功返回0,否则返回错误码。

pthread_join

pthread_join用来等待一个线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
声明如下:

extern int pthread_join __P ((pthread_t* restrict tidp, void **__thread_return));

(1)tidp:事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程ID。
(2)**__thread_return:为一个用户定义的指针,它可以用来存储被等待线程的返回值。

一个线程的结束有两种途径,一种是象我们上面的例子一样,等待函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。

pthread_exit

pthread_exit声明如下:

extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

唯一的参数是函数的返回代码。只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return

最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

1.3修改线程的属性

上文中例子中pthread_create函数创建线程中只使用了默认参数,即pthread_attr_t设为NULL。
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
下面是一个绑定的线程:

#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
设置线程分离状态的函数为
pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。
这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。

!注意不能使用wait()之类的函数。

另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

#include <pthread.h>
#include <sched.h> //包含sched_param等函数的头文件库
pthread_attr_t attr;//线程属性
pthread_t tid;//线程ID
sched_param param;//优先级参数
int newprio=20;

pthread_attr_init(&attr);//初始化
pthread_attr_getschedparam(&attr, &param);//先取优先级参数
param.sched_priority=newprio;//优先级赋值
pthread_attr_setschedparam(&attr, &param);//存放优先级参数
pthread_create(&tid, &attr, (void *)myfunction, myarg);//创建线程

1.4线程的数据处理

线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。
在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。

1.4.1 线程数据

在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很像,在线程内部,各个函数可以像使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量error,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
  和线程数据相关的函数主要有4个:

	创建一个键;
  为一个键指定线程数据;
  从一个键读取线程数据;
  删除键。

创建键的函数原型为:

extern int pthread_key_create __P ((pthread_key_t *__key,void (*__destr_function) (void *)))

pthread_key_t *__key为指向一个键值的指针,void (*__destr_function) (void *))指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数

pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))

一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。

在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。

/* 声明一个键*/
pthread_key_t myWinKey;
/* 函数 createWindow */
void createWindow ( void ) {
/*创建一个窗口*/
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 调用函数createMyKey,创建键*/
pthread_once ( & once, createMyKey) ;
/*win指向一个新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific ( myWinKey, win);
}

/* 函数 createMyKey,创建一个键,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}

/* 函数 freeWinKey,释放空间*/
void freeWinKey ( Fl_Window * win){
delete win;
}

这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:

extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
extern void *pthread_getspecific __P ((pthread_key_t __key));

这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。

1.4.2 互斥锁

互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
  我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATEPTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVEPTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
  pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。
  上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

1.4.3 条件变量

前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
  条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:

extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));

其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 
  函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:

extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));

线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
  另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:

extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));

它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
  函数pthread_cond_signal()的原型为:

extern int pthread_cond_signal __P ((pthread_cond_t *__cond));

它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}

increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}

count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
  函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。

1.4.4 信号量

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:

extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));

sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
  函数sem_destroy(sem_t *sem)用来释放信号量sem。
  下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。

/* File sem.c */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 从文件1.dat读取数据,每读一次,信号量加一*/
void ReadData1(void){
	FILE *fp=fopen("1.dat","r");
	while(!feof(fp)){
		fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
		sem_post(&sem);
		++size;
		}
	fclose(fp);
	}
/*从文件2.dat读取数据*/
void ReadData2(void){
	FILE *fp=fopen("2.dat","r");
	while(!feof(fp)){
		fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
		sem_post(&sem);
		++size;
		}
	fclose(fp);
	}
/*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/
void HandleData1(void){
	while(1){
		sem_wait(&sem);
		printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
		stack[size][0]+stack[size][1]);
		--size;
		}
	}

void HandleData2(void){
	while(1){
		sem_wait(&sem);
		printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
		stack[size][0]*stack[size][1]);
		--size;
		}
	}
int main(void){
	pthread_t t1,t2,t3,t4;
	sem_init(&sem,0,0);
	pthread_create(&t1,NULL,(void *)HandleData1,NULL);
	pthread_create(&t2,NULL,(void *)HandleData2,NULL);
	pthread_create(&t3,NULL,(void *)ReadData1,NULL);
	pthread_create(&t4,NULL,(void *)ReadData2,NULL);
	/* 防止程序过早退出,让它在此无限期等待*/
	pthread_join(t1,NULL);
	}

在Linux下,我们用命令

gcc -o sem sem.c -lpthread

生成可执行文件sem。 我们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我们运行sem,得到如下的结果:
Multiply:34=12
Multiply:1
2=2
Multiply:00=0
Multiply:0
0=0
Multiply:-9*-10=90
Plus:-9±10=-19
Plus:-5±6=-11
Plus:-3±4=-7
Plus:0+0=0
Plus:0+0=0
Plus:9+10=19
Multiply:-7*-8=56

从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是多线程编程要注意的问题。

2进程

在传统的Unix环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致,只在一些细节的地方有些区别,例如在Linux系统中调用vfork和fork完全相同,而在有些版本的Unix系统中,vfork调用有不同的功能。
fork()

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
 
int main(int argc,char *argv[]){
	pid_t fpid = fork();
	if (fpid < 0 ) {
		fprintf(stderr,"ERROR!");
	} else if(fpid == 0 ) {
		printf("child process,child process pid为%d.\n",getpid());
		exit(0);
	}
	else {
		printf("parent process,child process pid为%d.\n",getpid());
	}
	// 可以使用wait或waitpid函数等待子进程的结束并获取结束状态
	return 0;
}

运行结果:

parent process,child process pid为4025.
child process,child process pid为4026.

函数原型为pid_t fork(void)(pid_t是一个宏定义,实质是int型,定义头文件再<sys/types.h>中。fork()函数无参数传入,被成功调用一次后会返回两个值,子进程return 0,父进程return 子进程ID;若未成功调用则return -1。

2.1 切换进程

在Linux中要使用exec函数族。系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。exec函数族当然不止一个,但它们大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,下面以execv为例,

//文件execvp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execvp(const char *file, char *const argv[]);

int main(void)
{
    printf("before execlp****\n");
    char *argv[] = {"upper","./upper",NULL};
    pid_t fpid = fork();
    
    if (fpid < 0 ) {
		fprintf(stderr,"error!");
	} else if(fpid == 0 ) {
		printf("child process,child process pid为%d.\n",getpid());
	}
	else {
		printf("parent process,child process pid为%d.\n",getpid());
		if(execvp("upper",argv) == -1) 
        {
            printf("execvp failed!\n");     
        }
    	printf("after execlp*****\n");
	}
    return 0;
}
}

需要给系统设置当前路径的环境变量
命令行输入

export PATH $PATH=/你的文件路径
//文件upper.c
#include <stdio.h>
#include <ctype.h>

int main(void)
{
	printf("hello world!\n");
	return 0;
}

执行结果

before execlp****
parent process,child process pid为3602.
child process,child process pid为3603.
hello world!

在父进程中,由于使用execvp函数,使父进程跳转执行upper程序,故父进程中printf("after execlp*****\n");就不再执行。

其余exec函数族函数定义参考另一篇博客博客

2.2进程间通信

管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。
   无名管道由pipe()函数创建:

#include <unistd.h>
int pipe(int filedis[2])

参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
例:

/*pipeline be used to communicate between child process and parent process*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define INPUT 0
#define OUTPUT 1

void main() {   
    int file_descriptors[2];   
    /*定义子进程号 */   
    pid_t pid;   
    char buf[256];   
    int returned_count;   
    /*创建无名管道*/   
    pipe(file_descriptors);   
    /*创建子进程*/   
    if((pid = fork()) == -1) {   
        printf("Error in fork\n");   
        exit(1);   
        }   
    /*执行子进程*/   
    if(pid == 0) {   
        printf("in the spawned (child) process...\n");   
        /*子进程向父进程写数据,关闭管道的读端*/   
        close(file_descriptors[INPUT]);   
        write(file_descriptors[OUTPUT], "test data", strlen("test data"));   
        exit(0);   
    } else {   
        /*执行父进程*/   
        printf("in the spawning (parent) process...\n");   
        /*父进程从管道读取子进程写的数据,关闭管道的写端*/   
        close(file_descriptors[OUTPUT]);   
        returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));   
        printf("%d bytes of data received from spawned process: %s . \n", returned_count, buf); 
    }   
}   

输出:

in the spawning (parent) process...
in the spawned (child) process...
9 bytes of data received from spawned process: test data��� . 

但是test data 后边会有三个乱码,暂时还没找到问题所在。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PX4学习之uorb 的相关文章

  • docker常用镜像命令总结,包含将docker镜像推送到dockerhub,全程无废话

    01 查看镜像 docker images span class token punctuation span root 64 i rbj0ryi8 span class token punctuation span span class
  • 无人机-2多翼无人机的结构与硬件

    一 多旋翼无人机的机体布局 1 多旋翼无人机的结构布局 动力输出轴上判断几轴飞行器 动力轴越多 xff0c 飞行稳定性相对越好 xff0c 可靠性相对降低 xff08 模块多 xff0c 隐患多 xff09 常规固定式 xff1a 分为带边
  • 无人机-4无人机结构设计

    一 需求分析 概述 xff1a 设计上的纸面参数做到一个切实用到飞机上的一个设计参数的一个过程 1 飞机的任务是什么 针对设计目的有不同结构需求的 物流无人机 xff1a 仓储位置 摄影无人机 xff1a 拍照 xff0c 搭载稳定的三轴云
  • ubuntu20.04安装ros noetic以及创建ros工作空间catkin_ws

    章节目录 一 安装ROS1 设置下载源2 安装依赖项3 安装ros4 配置环境变量5 运行ROS6 rosdep初始化7 安装rosinstall8 启动例子 xff08 小海龟 xff09 二 创建ROS工作空间 catkin ws1 创
  • MATLAB学习笔记(注释超详细)

    1 matlab数据类型转换 xff0c 例 xff1a uint8函数将数值数据转换成无符号8位整数 xff0c int8将数值数据转换为带符号8位整数 x 61 int8 129 因为带符号8位整型数的最大值为127 xff0c 129
  • 一、SLAM算法与导航实战(毕设开源分享)

    目录 1 开源分享 2 建图结果 2 1 多观测点环境结果 xff1a 2 2 长廊环境建图结果 xff1a 1 开源分享 今天终于完成了毕设项目的落地和算法实现 xff0c 比较感叹这里面的坑 xff0c 不仅仅是Linux ROS xf
  • 【3D视觉工坊】第二期公开课:相机标定的基本原理与经验分享 笔记

    课程链接 xff1a 3D视觉工坊 第二期公开课 xff1a 相机标定的基本原理与经验分享 哔哩哔哩 bilibili
  • writeup-RrEeGgEeXx EKOPARTY

    State of the art on authentication mechanisms 题目如上 xff0c 下载完附件 xff0c 扔进IDA xff0c 发现又是c 的 于是又扔进reflector反编译一下 分析了下发现有一个对字
  • FreeRTOS 学习笔记(自用)

    前言 xff1a 本文章用于记录学习FreeRTOS xff08 韦老师 xff09 期间的笔记以及一些个人理解 xff08 带完善 xff09 一 学习内容 xff1a 1 裸机及操作系统 2 FreeRTOS功能及其实现原理 3 线程通
  • 《单片机串口》—将传感器获取的数值在上位机显示

    这里写目录标题 1 单片机通过串口发送单个字符2 单片机通过串口发送字符串3 单片机将传感器获取到的数字值发送到上位机 之前在学蓝牙通信的时候如何将单片机获取到的传感器的数值发送到上位机遇到了不少的问题 由于数据的发送一种是字符格式一种是
  • 三天让车立起来!STM32平衡车入门PID —— 第三天(PID调参)

    说明 xff1a 本文章适用于STM32初学者 xff0c 想完成一个好玩且有深度的项目但不知道从何下手的同学 PID调参是平衡车的精髓所在 xff0c 参数整定的好坏直接影响到平衡车的平衡效果 有的车平衡时来回晃而参数选的好的车就能稳稳地
  • PCB笔记-原理图

    1 学习路线 画PCB追求的是效率 xff01 xff01 xff01 2 元件库的创建 2 1 元件符号 元件符号是元件在原理图上的表现形式 xff0c 主要由元件边框 xff0c 管脚 xff08 包括管脚序号和管脚名称 xff09 x
  • FreeRTOS任务调度启动流程

    FreeRTOS任务调度启动流程 PrefacePreviewAttentionFreeRTos目录结构解释 FreeRTos任务调度一 vTaskStartScheduler函数中需要注意的细节二 xPortStartScheduler函
  • 正点原子视频学习笔记—Verilog下按键消抖, 实现LED灯的点亮和熄灭

    正点原子视频学习笔记 Verilog下按键消抖 实现LED灯的点亮和熄灭 在学习FPGA的基础阶段会有按键控制LED灯亮灭的实验 xff0c 其中避免不了要对按键进行消抖处理 xff0c 以及LED的控制模块 xff0c 和顶层例化模块 本
  • 蚂蚁4面和体检都过了,最后却因为背调挂了导致无业,网友:没养个备胎?

    因为背调而与offer失之交臂的求职者并不是个例 xff0c 最近看到一位程序员发帖称 xff0c 自己蚂蚁4面和体检都过了 xff0c 最后却因为合并简历而挂了 着实令人可惜 原贴如下 xff1a 楼主在评论区解释称 xff0c 自己已经
  • Linux C 下的socket网络编程

    1 socket简介 在我们常用的网络通信中 xff0c socket是最常用的一种 xff0c socket编程也称套接字编程 xff0c 下面我们将对socket编程进行相关讲解 xff0c 其中包括服务器与客户端通信讲解 xff0c
  • 关于stm32结构体

    刚刚看到stm32结构体 xff0c 这个部分还是很重要的 xff0c 做一个小结总结一下 正常标注结构体形式 struct GPIO 成员列表 这样就定义了一个结构体 xff0c 但是当需要定义变量时就需要 struct GPIO GPI
  • stm32f103c8t6 + 串口打印 + dh1

    1 首先新建一个c8t6的工程 如果使用本程序的话 xff0c 可直接运行 xff0c 亲测有效 xff0c 如果想移植功能的同学继续往下看 程序里面的注释也写的很清楚 2 串口打印 首先拿到usart c和usart h文件放置自己的工程
  • 【VIBE: Video Inference for Human Body Pose and Shape Estimation】论文阅读

    论文连接 xff1a https arxiv org pdf 1912 05656 pdf 代码连接 xff1a https github com mkocabas VIBE Video Inference for Human Body P
  • 操作系统——处理机调度

    2 2 处理机调度 2 2 1 调度的概念 调度的基本概念 xff1a 处理机调度是对处理机进行分配 xff0c 从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行 xff0c 以实现进程并发执行 调度的层次 xff1a 1 xf

随机推荐

  • ROS学习指南(五)

    IMU在ROS里的使用方法 一 IMU消息包在ROS里的数据格式 IMU是安装在机器人内部的传感器模块 xff0c 用来测量当前机器人的位姿 常用的六轴IMU可以解算出机器人xyz三个方向的角速度和角加速度 消息包定义如下 xff0c 其中
  • vins-fusion环境配置,解决opencv3->4的cv::bridge问题

    本文主要介绍如何搭建vins fusion的运行环境 xff0c 以及解决vins fusion编译运行时遇到的环境冲突问题 xff0c 并在此基础上实现例程的运行 本文是在已经安装好 ROS 的基础上 xff0c 在 Ubuntu20 0
  • 解决ROS编译过程中的简单的环境问题

    一 rosmake ERROR No arguments could be parsed into valid package or stack names 该错误是因为找不到工作目录路径 xff0c 设置为当前绝对路径名即可 xff0c
  • 《headfirst设计模式》读书笔记9-迭代器和组合模式

    目录 1 封装遍历2 定义迭代器模式2 1 部分源码2 1 1 MenuItem h2 1 2 Menu h2 1 3 Iterator h2 1 4 ArrayIterator h2 1 5 ArrayIterator cpp2 1 6
  • 【论文笔记】Stereo Camera Localization in 3D LiDAR Maps

    论文笔记 Stereo Camera Localization in 3D LiDAR Maps 随着 3D 光探测和测距 LiDAR 传感器的出现 xff0c 同步定位和映射 SLAM 技术蓬勃发展 xff0c 因此准确的 3D 地图很容
  • C盘有哪些软件可以删除呀

    今天下载QQ群里面的文件 xff0c 电脑居然弹窗 磁盘空间不足 xff0c 我心头一痛 我不是上个月才清过的吗 xff1f xff01 好吧 xff0c 那再来一次 这是我删了一通之后的C盘状态 xff1a 是不是还是很满 xff1f 没
  • Kubernetes-集群结合普罗米修斯、监控nginx、hpa动态伸缩

    目录 xff1a Prometheus简介一 k8s集群部署Prometheus二 Prometheus监控应用nginx三 prometheus实现k8s集群的hpa动态伸缩 Prometheus简介 Prometheus 是由 Soun
  • antd的Form表单如何取到表单里的数据以及如何设置默认值

    获取表单数据 第一步创建ref实例 span class token comment 在render外面 xff0c 类里面就可以 span formRef span class token operator 61 span React s
  • linux知识点——CMakeLists.txt常见指令

    demo1 单文件 只有一个main cpp的情况 main cpp span class token macro property span class token directive hash span span class token
  • 架构师面试问题

    架构师面试问题 xff1a 如何设计高可用 如何设计高并发 如何设计高扩展 如何jvm调优及管控 如何复用及重构 1 使用那些设计模式解决生产中的问题 2 架构选型过程考虑 3 如何保证开发的代码质量实践及减少生产问题 4 如何保证稳定性
  • dockerfile基础介绍

    dockerfile 官网 xff1a https docs docker com engine reference builder 构建三步骤 编写Dockerfile文件docker build命令构建镜像docker run依镜像运行
  • 从prometheus生态系统组件到集成Java开发

    Prometheus介绍 Prometheus是最初在SoundCloud上构建的开源系统监视和警报工具包 于2016年加入了 Cloud Native Computing Foundation xff0c 这是继Kubernetes之后的
  • c++继承和多态

    文章目录 一 继承访问权限测试设计类A具有public protected private等不同属性的成员函数或变量 xff1b 类B通过public protected private等不同方式继承A xff0c 在类B的成员函数中测试访
  • DockerFile定义

    1 何为Dockerfile xff1f Dockerfile是用来构建Docker镜像的文本文件 xff0c 是由一条条构建镜像所需的指令和参数构成的脚本 2 构建三步骤 编写Dockerfile文件 docker build命令构建镜像
  • 死锁的成因及其解决办法

    x1f512 一 什么是死锁 死锁就是两个或者两个以上的线程在执行过程中 由于资源竞争或者由于彼此通信而造成的阻塞现象 若无外力作用 都将无法推进下去 二 关于死锁的情况 x1f937 情况 一个线程 一把锁 如果是可重入锁不会产生死锁 不
  • Ubuntu 18.04 配置ROS melodic 配置qt包并且建立基础pub界面

    建立工作空间 mkdir p ros gui demo src cd ros pkg src sudo apt get install ros melodic qt create sudo apt get install ros melod
  • RFID 负载调制和反向散射调制

    我用我质朴的语言概述一下我的理解 xff0c 射频识别系统中 xff0c 阅读器和标签之间的通信通过电磁波来实现 按照通信距离 xff0c 可以划分为近场和远场 xff1b 按照频率又分 xff1a 低频 高频 超高频 相应的 xff0c
  • ubuntu虚拟机上配置深度学习环境

    学习笔记 xff0c 主要是记录自己目前进行的操作 xff0c 以便自己回顾 xff0c 各位大佬有指导意见也欢迎交流啊 一 安装miniconda Linux下安装miniconda linux 安装miniconda 摸鱼肥仔的博客 C
  • 如何创建一个项目(学习笔记)

    前言 xff1a 欢迎来到本次教程 这篇文章旨在深入解析在 B 站上的 Vue3 后台管理项目 xff0c 同时也为您提供了创建此类项目的实践思路 我们将通过这个笔记系统地梳理一个项目的整体框架 xff0c 涵盖了我们在 Vue 课程中学习
  • PX4学习之uorb

    PX4学习之uorb 1线程 1 1优点 在Linux系统下 xff0c 启动一个新的进程必须分配给它独立的地址空间 xff0c 建立众多的数据表来维护它的代码段 堆栈段和数据段 xff0c 这是一种 34 昂贵 34 的多任务工作方式 而