linux 多執行緒串列埠程式設計總結
最近在玩DJI M100,呼叫API獲取GPS位置時發現高程定位完全是錯的(負的幾百多米),查了一下文件說高程資料是由氣壓計得到的,而飛行控制時又需要比較可靠的高度資訊,於是乎決定用上我們實驗室的搭載Ublox晶片的板子搞事情,在子執行緒通過串列埠接收板子的定位結果,在主執行緒呼叫,開發環境為Ubuntu16.04/14.04,前者為虛擬機器,後者為manifold。
1. 串列埠程式設計(只讀)
Linux串列埠程式設計的步驟很簡單,開啟串列埠---串列埠初始化---讀寫串列埠---關閉串列埠
int open_port(int fd) { fd = open( "/dev/ttyUSB0", O_RDWR); if (-1 == fd) { perror("Can't Open Serial Port ttyUSB0"); fd = open( "/dev/ttyUSB1", O_RDWR); } else printf("open ttyUSB0 .....\n"); if(fcntl(fd, F_SETFL, 0)<0) printf("fcntl failed!\n"); else printf("fcntl=%d\n",fcntl(fd, F_SETFL,0)); if(isatty(STDIN_FILENO)==0) printf("standard input is not a terminal device\n"); else printf("isatty success!\n"); printf("fd-open=%d\n",fd); return fd; }
主要函式:
Open()
pathname:檔案路徑名,串列埠在Linux中被看做是一個檔案
oflag:一些檔案模式選擇,有如下幾個引數可以設定
· O_RDONLY只讀模式
· O_WRONLY只寫模式
· O_RDWR讀寫模式
上面三種模式在傳參時必須傳入其中一個,另外還有幾個可供選擇
· O_NOCTTY如果路徑名指向終端裝置,不要把這個裝置用作控制終端。
· O_NONBLOCK設定為非阻塞模式(nonblocking mode)
· 等等。
Isatty,若為終端裝置則返回1(真),否則返回0(假)。
設定串列埠,包括波特率,校驗方式,停止位,資料位等等。詳見程式碼。int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop) { struct termios newtio,oldtio; if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1"); return -1; } bzero( &newtio, sizeof( newtio ) ); newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; switch( nBits ) { case 7: newtio.c_cflag |= CS7; break; case 8: newtio.c_cflag |= CS8; break; } switch( nEvent ) { case 'O': newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); break; case 'E': newtio.c_iflag |= (INPCK | ISTRIP); newtio.c_cflag |= PARENB; newtio.c_cflag &= ~PARODD; break; case 'N': newtio.c_cflag &= ~PARENB; break; } switch( nSpeed ) { case 2400: cfsetispeed(&newtio, B2400); cfsetospeed(&newtio, B2400); break; case 4800: cfsetispeed(&newtio, B4800); cfsetospeed(&newtio, B4800); break; case 9600: cfsetispeed(&newtio, B9600); cfsetospeed(&newtio, B9600); break; case 115200: cfsetispeed(&newtio, B115200); cfsetospeed(&newtio, B115200); break; default: cfsetispeed(&newtio, B9600); cfsetospeed(&newtio, B9600); break; } if( nStop == 1 ) newtio.c_cflag &= ~CSTOPB; else if ( nStop == 2 ) newtio.c_cflag |= CSTOPB; newtio.c_cc[VTIME] = 5; newtio.c_cc[VMIN] = 50; tcflush(fd,TCIFLUSH); if((tcsetattr(fd,TCSANOW,&newtio))!=0) { perror("com set error"); return -1; } printf("set done!\n"); return 0; }
注意:newtio.c_cc[VTIME]= 5;newtio.c_cc[VMIN] = 50;貌似將串列埠讀改成了阻塞模式。
2. 多執行緒
執行緒的操作主要步驟是建立執行緒,互斥鎖和訊號量來保證執行緒同步並保護資料。
執行緒建立函式
int temp = 100;
pthread_t thread;//定義執行緒
temp = pthread_create(&thread,NULL,Serialthread,NULL);
if(temp != 0)
{
printf("Create thread failed!");
return 0;
}
Serialthread為執行緒函式,執行緒在此處建立時即執行該函式。然後同時地執行該語句後的主執行緒部分,若主執行緒結束,則子執行緒也會跟著結束。因此子執行緒中需要一個死迴圈一直從串列埠讀取GPS資料,同樣的主執行緒中也應該通過迴圈呼叫串列埠讀出來的資料。
同時如果在其他的原始檔中也需要這個子執行緒中獲取到的資料,只需要在原始檔對應的標頭檔案中宣告extern(型別) (變數名)即可使用。
為保證資料在串列埠讀取更新和在主函式中呼叫時不亂套,能夠同步安全地進行,因此需要引入互斥鎖和訊號量。何為互斥鎖,簡單來說,就是在一個執行緒中訪問公共資料(如這裡的GPS位置)之前將資料鎖住,使得其他執行緒不能再訪問,訪問操作結束之後再解鎖是的其他執行緒可以訪問或者操作。
主要函式包括pthread_mutex_init,初始化執行緒鎖;pthread_mutex_lock進入執行緒鎖;pthread_mutex_unlock,解鎖。
何為訊號量,在進入一個關鍵程式碼段之前,執行緒必須獲取一個訊號量;一旦該關鍵程式碼段完成了,那麼該執行緒必須釋放訊號量。其它想進入該關鍵程式碼段的執行緒必須等待直到第一個執行緒釋放訊號量。
主要函式包括sem_init,訊號量初始化;sem_post,傳送訊號量;sem_wait,等待訊號量。
這樣的話,在子執行緒和主執行緒裡對資料進行操作前先鎖住,操作完再解鎖,子執行緒讀取資料之後傳送訊號量,主執行緒中對資料進行操作之前先等待訊號量。
我這裡用了DJI寫好的兩個類,用起來很方便。DJI_lock::DJI_lock()
{
pthread_mutex_init( &m_lock, NULL );
}
DJI_lock::~DJI_lock()
{
}
void DJI_lock::enter()
{
pthread_mutex_lock( &m_lock );
}
void DJI_lock::leave()
{
pthread_mutex_unlock( &m_lock );
}
DJI_event::DJI_event()
{
sem_init( &m_sem, 0, 0 );
}
DJI_event::~DJI_event()
{
}
int DJI_event::set_event()
{
int ret = sem_post( &m_sem );
return ret;
}
int DJI_event::wait_event()
{
int ret = sem_wait( &m_sem );
return ret;
}
具體實現(包括DJI的兩個類)可見demo(下載地址http://download.csdn.net/download/innocent_sheld/10226352),有問題歡迎討論。
另外還要提的一個是執行緒操作的pthread_join函式,用來等待一個執行緒的結束。描述 :pthread_join()函式,以阻塞的方式等待thread指定的執行緒結束。當函式返回時,被等待執行緒的資源被收回。如果執行緒已經結束,那麼該函式會立即返回。並且thread指定的執行緒必須是joinable的。
引數 :thread:執行緒識別符號,即執行緒ID,標識唯一執行緒。retval: 使用者定義的指標,用來儲存被等待執行緒的返回值。
返回值 : 0代表成功。失敗,返回的則是錯誤號。下面是一個小demo。
thread_para son1 = {'k', 10};
thread_para son2 = {'o', 20};
void *sonthread(void *argv)
{
thread_para *son;
son = (thread_para *)argv;
for (int k = 0; k < son->count; k++)
{
fputc(son->character, stderr);
usleep(1000);
}
printf("\n");
return (void *)23333;
}
int main(int argc, char **argv)
{
pthread_t thread1;
pthread_create(&thread1, NULL, sonthread, (void *)&son1);
pthread_t thread2;
pthread_create(&thread2, NULL, sonthread, (void *)&son2);
int res1, res2;
pthread_join(thread1, (void *)&res1);
pthread_join(thread2, (void *)&res2);
printf("res1 = %d\n", res1);
printf("res2 = %d\n", res2);
return 0;
}
執行結果:
這個demo的下載地址http://download.csdn.net/download/innocent_sheld/10226323
在主執行緒中建立了兩個執行緒,但呼叫的是同一個執行緒函式,不同的執行緒有不同的引數,執行緒建立時即執行執行緒函式,因此會依此列印o和k,在主執行緒中又呼叫了兩次pthread_join函式,因此主執行緒要等待兩個子執行緒結束才能執行後面的語句。注意:如果這裡(void *)&提示錯誤的話error: invalid conversionfrom ‘void*’ to ‘void**’應該使用gcc編譯,不能使用g++。大概是因為C++不會將這裡的(void*)&解析為void**。
如果您覺得我的部落格對您有所幫助,歡迎對我進行小額資助,以幫助我寫出更多博文。:)