linux应用基础编程及优化调试

1、多线程及同步

linux多线程api:pthread_equal 、 pthread_self 、 pthread_create 、 pthread_exit 、pthread_join、pthread_cancel、pthread_cleanup_push、pthread_cleanup_pop、pthread_detach、pthread_mutex_init、pthread_mutex_destroy

pthread_join用于等待线程结束,常用于主线程结束时等待其他线程结束。

主线程或其他线程中往往还会创建线程,如果线程退出时一般发生的情况:

a、主线程通过pthread_join、sleep等待其他线程退出,然后程序正常结束。

b、exit(0)退出,进程退出,所有的线程也退出并释放资源。

c、主线程调用pthread_exit退出,主线程退出,其他线程依然运行。

线程同步互斥锁、读写锁、条件变量、信号量。

信号量是计数的方式,可以用于进程间、互斥锁是对变量的互斥保护。

条件变量是用于睡眠等待资源的,不加锁,一般条件变量与互斥锁配合使用。

互斥锁函数:pthread_mutex_lock/trylock/unlock。

条件变量:pthread_cond_wait/signal/broadcast

  1. #include <pthread.h>  
  2. #include <unistd.h>  
  3. #include "stdio.h"  
  4. #include "stdlib.h"  
  5. static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;  
  6. static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  
  7. struct node  
  8. {  
  9.     int n_number;  
  10.     struct node *n_next;  
  11. }*head = NULL;  
  12.   
  13. static void cleanup_handler(void *arg)  
  14. {  
  15.     printf("Cleanup handler of second thread./n");  
  16.     free(arg);  
  17.     (void)pthread_mutex_unlock(&mtx);  
  18. }  
  19. static void *thread_func(void *arg)  
  20. {  
  21.     struct node *p = NULL;  
  22.     pthread_cleanup_push(cleanup_handler, p);  
  23.     while (1)  
  24.     {  
  25.         //这个mutex主要是用来保证pthread_cond_wait的并发性  
  26.         pthread_mutex_lock(&mtx);  
  27.         while (head == NULL)  
  28.         {  
  29.             //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何  
  30.             //这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线  
  31.             //程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。  
  32.             //这个时候,应该让线程继续进入pthread_cond_wait  
  33.             // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,  
  34.             //然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立  
  35.             //而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源  
  36.             //用这个流程是比较清楚的  
  37.             pthread_cond_wait(&cond, &mtx);  
  38.             p = head;  
  39.             head = head->n_next;  
  40.             printf("Got %d from front of queue/n", p->n_number);  
  41.             free(p);  
  42.         }  
  43.         pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁  
  44.     }  
  45.     pthread_cleanup_pop(0);  
  46.     return 0;  
  47. }  
  48. int main(void)  
  49. {  
  50.     pthread_t tid;  
  51.     int i;  
  52.     struct node *p;  
  53.     //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而  
  54.     //不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大  
  55.     pthread_create(&tid, NULL, thread_func, NULL);  
  56.     sleep(1);  
  57.     for (i = 0; i < 10; i++)  
  58.     {  
  59.         p = (struct node*)malloc(sizeof(struct node));  
  60.         p->n_number = i;  
  61.         pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,  
  62.         p->n_next = head;  
  63.         head = p;  
  64.         pthread_cond_signal(&cond);  
  65.         pthread_mutex_unlock(&mtx); //解锁  
  66.         sleep(1);  
  67.     }  
  68.     printf("thread 1 wanna end the line.So cancel thread 2./n");  
  69.     //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出  
  70.     //线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。  
  71.     pthread_cancel(tid);  
  72.     pthread_join(tid, NULL);  
  73.     printf("All done -- exiting/n");  
  74.     return 0;  
  75. }  

2、进程编程及进程间通讯

新建进程:

a、fork和exec实现,fork复制父进程,exec替换当前进程。

因此一般用法时,在父进程中调用fork,根据fork返回值确定执行代码是父进程还是子进程,父进程代码wait等待子进程执行完成,子进程代码通过exec开启一个进程替换当前进程。

 fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。于是起初我就感到奇怪,子进程的物理空间没有代码,怎么去取指令执行exec系统调用呢?!原来在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

  1.   
  2. if (fork() == 0){  
  3.     //child process  
  4.     char * execv_str[] = {"echo""executed by execv",NULL};  
  5.     if (execv("/usr/bin/echo",execv_str) <0 ){  
  6.         perror("error on exec");  
  7.         exit(0);  
  8.     }  
  9. }else{  
  10.     //parent process  
  11.     wait();  
  12.     printf("execv done\n\n");  
  13. }  
b、clone

fork()是全部复制,vfork()是共享内存,vfork时父进程阻塞,而clone()是则可以将父进程资源有选择地复制给子进程,

而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags来决定。

另外,clone()返回的是子进程的pid。
fork的实现:
   do_fork(CLONE_SIGCHLD,...)
clone的实现:
    do_fork(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGCHLD,...)
vfork的实现:
     do_fork(CLONE_VFORK|CLONE_VM|CLONE_SIGCHLD,...)
为了优化那些:fork然后就是exec的程序,Linux提供了vforkvfork时,父进程会被阻塞,直到子进程调用了execexit,因为此时不复制页表结构。
clone()系统调用是fork()的推广形式,它允许新进程共享父进程的存储空间、文件描述符和信号处理程序

fork, vfork and clone三者最终都会调用do_fork函数,三者的差别就是参数上的不同而已。

实际上产生效果的也是这些参数:

CLONE_VM标识:表示共享地址空间(变量等)

CLONE_FILES标志:表示共享文件描述符表

CLONE_VFORK标识:标识父进程会被阻塞,子进程会把父进程的地址空间锁住,直到子进程退出或执行exec时才释放该锁

SIGCHLD标识:共享信号

 Linux使用copy on wirte的技术,Linux中的fork代价仅仅是创建子进程的页表结构和创建一个task_struct结构。


进程间通讯

a、signal

信号用于进程间交互,内核为每个进程维护一个信号掩码,类似信号的队列,但同类型的信号只能存储一个。

信号的来源:硬件事件(一些按键触发)、非法运算、系统函数调用(kill, raise, alarm、setitimer、sigqueue)等。

信号处理:忽略、系统默认处理、用户自定义处理。SIGKILL、SIGSTOP即不能被捕捉也不能被忽略。

b、管道

管道是环形队列,半双工模式,且存储缓冲区有限。半双工,读时要关闭写,写时要关闭读。管道只能应用于有亲缘关系的进程之间:

int pipe(int filedes[2]);

filedes[0]用于读,使用时要close(filedes[1])

filedes[1]用于写,使用时要close(filedes[0])

命名管道可以应用于不存在亲缘关系的进程之间:

int mkfifo(const char *filename, mode_t mode);
int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );
c、socket

socket用于进程间通讯。netlink是一种基于socket实现的用户和进程间交互的方式。

d、消息队列

消息队列可以可以避免管道的同步和阻塞问题,不需要进程自己来管理同步问题。消息队列可以选择接收的消息类型。

通过ipcs -l可以查看到系统支持的ipc资源空间。

接收消息进程:

  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <errno.h>  
  6. #include <sys/msg.h>  
  7.   
  8. struct msg_st  
  9. {  
  10.     long int msg_type;  
  11.     char text[BUFSIZ];  
  12. };  
  13.   
  14. int main()  
  15. {  
  16.     int running = 1;  
  17.     int msgid = -1;  
  18.     struct msg_st data;  
  19.     long int msgtype = 0; //注意1  
  20.   
  21.     //建立消息队列  
  22.     msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  
  23.     if(msgid == -1)  
  24.     {  
  25.         fprintf(stderr, "msgget failed with error: %d\n", errno);  
  26.         exit(EXIT_FAILURE);  
  27.     }  
  28.     //从队列中获取消息,直到遇到end消息为止  
  29.     while(running)  
  30.     {  
  31.         if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)  
  32.         {  
  33.             fprintf(stderr, "msgrcv failed with errno: %d\n", errno);  
  34.             exit(EXIT_FAILURE);  
  35.         }  
  36.         printf("You wrote: %s\n",data.text);  
  37.         //遇到end结束  
  38.         if(strncmp(data.text, "end", 3) == 0)  
  39.             running = 0;  
  40.     }  
  41.     //删除消息队列  
  42.     if(msgctl(msgid, IPC_RMID, 0) == -1)  
  43.     {  
  44.         fprintf(stderr, "msgctl(IPC_RMID) failed\n");  
  45.         exit(EXIT_FAILURE);  
  46.     }  
  47.     exit(EXIT_SUCCESS);  
  48. }  
发送消息进程:

  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <sys/msg.h>  
  6. #include <errno.h>  
  7.   
  8. #define MAX_TEXT 512  
  9. struct msg_st  
  10. {  
  11.     long int msg_type;  
  12.     char text[MAX_TEXT];  
  13. };  
  14.   
  15. int main()  
  16. {  
  17.     int running = 1;  
  18.     struct msg_st data;  
  19.     char buffer[BUFSIZ];  
  20.     int msgid = -1;  
  21.   
  22.     //建立消息队列  
  23.     msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  
  24.     if(msgid == -1)  
  25.     {  
  26.         fprintf(stderr, "msgget failed with error: %d\n", errno);  
  27.         exit(EXIT_FAILURE);  
  28.     }  
  29.   
  30.     //向消息队列中写消息,直到写入end  
  31.     while(running)  
  32.     {  
  33.         //输入数据  
  34.         printf("Enter some text: ");  
  35.         fgets(buffer, BUFSIZ, stdin);  
  36.         data.msg_type = 1;    //注意2  
  37.         strcpy(data.text, buffer);  
  38.         //向队列发送数据  
  39.         if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)  
  40.         {  
  41.             fprintf(stderr, "msgsnd failed\n");  
  42.             exit(EXIT_FAILURE);  
  43.         }  
  44.         //输入end结束输入  
  45.         if(strncmp(buffer, "end", 3) == 0)  
  46.             running = 0;  
  47.         sleep(1);  
  48.     }  
  49.     exit(EXIT_SUCCESS);  
  50. }  

e、共享内存

共享内存没有自动同步机制,一般要结合信号量实现同步。

共享内存的优点是使用方便,无进程关系限制,速度快。

共享内存读:

  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4. #include <sys/shm.h>  
  5. #include "shmdata.h"  
  6.   
  7. int main()  
  8. {  
  9.     int running = 1;//程序是否继续运行的标志  
  10.     void *shm = NULL;//分配的共享内存的原始首地址  
  11.     struct shared_use_st *shared;//指向shm  
  12.     int shmid;//共享内存标识符  
  13.     //创建共享内存  
  14.     shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  
  15.     if(shmid == -1)  
  16.     {  
  17.         fprintf(stderr, "shmget failed\n");  
  18.         exit(EXIT_FAILURE);  
  19.     }  
  20.     //将共享内存连接到当前进程的地址空间  
  21.     shm = shmat(shmid, 0, 0);  
  22.     if(shm == (void*)-1)  
  23.     {  
  24.         fprintf(stderr, "shmat failed\n");  
  25.         exit(EXIT_FAILURE);  
  26.     }  
  27.     printf("\nMemory attached at %X\n", (int)shm);  
  28.     //设置共享内存  
  29.     shared = (struct shared_use_st*)shm;  
  30.     shared->written = 0;  
  31.     while(running)//读取共享内存中的数据  
  32.     {  
  33.         //没有进程向共享内存定数据有数据可读取  
  34.         if(shared->written != 0)  
  35.         {  
  36.             printf("You wrote: %s", shared->text);  
  37.             sleep(rand() % 3);  
  38.             //读取完数据,设置written使共享内存段可写  
  39.             shared->written = 0;  
  40.             //输入了end,退出循环(程序)  
  41.             if(strncmp(shared->text, "end", 3) == 0)  
  42.                 running = 0;  
  43.         }  
  44.         else//有其他进程在写数据,不能读取数据  
  45.             sleep(1);  
  46.     }  
  47.     //把共享内存从当前进程中分离  
  48.     if(shmdt(shm) == -1)  
  49.     {  
  50.         fprintf(stderr, "shmdt failed\n");  
  51.         exit(EXIT_FAILURE);  
  52.     }  
  53.     //删除共享内存  
  54.     if(shmctl(shmid, IPC_RMID, 0) == -1)  
  55.     {  
  56.         fprintf(stderr, "shmctl(IPC_RMID) failed\n");  
  57.         exit(EXIT_FAILURE);  
  58.     }  
  59.     exit(EXIT_SUCCESS);  
  60. }  
共享内存写:

  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <sys/shm.h>  
  6. #include "shmdata.h"  
  7.   
  8. int main()  
  9. {  
  10.     int running = 1;  
  11.     void *shm = NULL;  
  12.     struct shared_use_st *shared = NULL;  
  13.     char buffer[BUFSIZ + 1];//用于保存输入的文本  
  14.     int shmid;  
  15.     //创建共享内存  
  16.     shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  
  17.     if(shmid == -1)  
  18.     {  
  19.         fprintf(stderr, "shmget failed\n");  
  20.         exit(EXIT_FAILURE);  
  21.     }  
  22.     //将共享内存连接到当前进程的地址空间  
  23.     shm = shmat(shmid, (void*)0, 0);  
  24.     if(shm == (void*)-1)  
  25.     {  
  26.         fprintf(stderr, "shmat failed\n");  
  27.         exit(EXIT_FAILURE);  
  28.     }  
  29.     printf("Memory attached at %X\n", (int)shm);  
  30.     //设置共享内存  
  31.     shared = (struct shared_use_st*)shm;  
  32.     while(running)//向共享内存中写数据  
  33.     {  
  34.         //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本  
  35.         while(shared->written == 1)  
  36.         {  
  37.             sleep(1);  
  38.             printf("Waiting...\n");  
  39.         }  
  40.         //向共享内存中写入数据  
  41.         printf("Enter some text: ");  
  42.         fgets(buffer, BUFSIZ, stdin);  
  43.         strncpy(shared->text, buffer, TEXT_SZ);  
  44.         //写完数据,设置written使共享内存段可读  
  45.         shared->written = 1;  
  46.         //输入了end,退出循环(程序)  
  47.         if(strncmp(buffer, "end", 3) == 0)  
  48.             running = 0;  
  49.     }  
  50.     //把共享内存从当前进程中分离  
  51.     if(shmdt(shm) == -1)  
  52.     {  
  53.         fprintf(stderr, "shmdt failed\n");  
  54.         exit(EXIT_FAILURE);  
  55.     }  
  56.     sleep(2);  
  57.     exit(EXIT_SUCCESS);  
  58. }  

f、信号量

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。

  1. #include <unistd.h>  
  2. #include <sys/types.h>  
  3. #include <sys/stat.h>  
  4. #include <fcntl.h>  
  5. #include <stdlib.h>  
  6. #include <stdio.h>  
  7. #include <string.h>  
  8. #include <sys/sem.h>  
  9.   
  10. union semun  
  11. {  
  12.     int val;  
  13.     struct semid_ds *buf;  
  14.     unsigned short *arry;  
  15. };  
  16.   
  17. static int sem_id = 0;  
  18.   
  19. static int set_semvalue();  
  20. static void del_semvalue();  
  21. static int semaphore_p();  
  22. static int semaphore_v();  
  23.   
  24. int main(int argc, char *argv[])  
  25. {  
  26.     char message = 'X';  
  27.     int i = 0;  
  28.   
  29.     //创建信号量  
  30.     sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);  
  31.   
  32.     if(argc > 1)  
  33.     {  
  34.         //程序第一次被调用,初始化信号量  
  35.         if(!set_semvalue())  
  36.         {  
  37.             fprintf(stderr, "Failed to initialize semaphore\n");  
  38.             exit(EXIT_FAILURE);  
  39.         }  
  40.         //设置要输出到屏幕中的信息,即其参数的第一个字符  
  41.         message = argv[1][0];  
  42.         sleep(2);  
  43.     }  
  44.     for(i = 0; i < 10; ++i)  
  45.     {  
  46.         //进入临界区  
  47.         if(!semaphore_p())  
  48.             exit(EXIT_FAILURE);  
  49.         //向屏幕中输出数据  
  50.         printf("%c", message);  
  51.         //清理缓冲区,然后休眠随机时间  
  52.         fflush(stdout);  
  53.         sleep(rand() % 3);  
  54.         //离开临界区前再一次向屏幕输出数据  
  55.         printf("%c", message);  
  56.         fflush(stdout);  
  57.         //离开临界区,休眠随机时间后继续循环  
  58.         if(!semaphore_v())  
  59.             exit(EXIT_FAILURE);  
  60.         sleep(rand() % 2);  
  61.     }  
  62.   
  63.     sleep(10);  
  64.     printf("\n%d - finished\n", getpid());  
  65.   
  66.     if(argc > 1)  
  67.     {  
  68.         //如果程序是第一次被调用,则在退出前删除信号量  
  69.         sleep(3);  
  70.         del_semvalue();  
  71.     }  
  72.     exit(EXIT_SUCCESS);  
  73. }  
  74.   
  75. static int set_semvalue()  
  76. {  
  77.     //用于初始化信号量,在使用信号量前必须这样做  
  78.     union semun sem_union;  
  79.   
  80.     sem_union.val = 1;  
  81.     if(semctl(sem_id, 0, SETVAL, sem_union) == -1)  
  82.         return 0;  
  83.     return 1;  
  84. }  
  85.   
  86. static void del_semvalue()  
  87. {  
  88.     //删除信号量  
  89.     union semun sem_union;  
  90.   
  91.     if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)  
  92.         fprintf(stderr, "Failed to delete semaphore\n");  
  93. }  
  94.   
  95. static int semaphore_p()  
  96. {  
  97.     //对信号量做减1操作,即等待P(sv)  
  98.     struct sembuf sem_b;  
  99.     sem_b.sem_num = 0;  
  100.     sem_b.sem_op = -1;//P()  
  101.     sem_b.sem_flg = SEM_UNDO;  
  102.     if(semop(sem_id, &sem_b, 1) == -1)  
  103.     {  
  104.         fprintf(stderr, "semaphore_p failed\n");  
  105.         return 0;  
  106.     }  
  107.     return 1;  
  108. }  
  109.   
  110. static int semaphore_v()  
  111. {  
  112.     //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)  
  113.     struct sembuf sem_b;  
  114.     sem_b.sem_num = 0;  
  115.     sem_b.sem_op = 1;//V()  
  116.     sem_b.sem_flg = SEM_UNDO;  
  117.     if(semop(sem_id, &sem_b, 1) == -1)  
  118.     {  
  119.         fprintf(stderr, "semaphore_v failed\n");  
  120.         return 0;  
  121.     }  
  122.     return 1;  
  123. }  

3、linux应用定时任务

a、精度要求不高的定时可以用sleep、usleep。

b、alarm、signal。精度不高。

c、setitimer,精度高,三种定时模式,配合信号使用。

4、多路复用技术

多路复用即同步I/O,select、poll、epoll是实现同步I/O的技术。

select:

  1. #include <sys/select.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6. #include <unistd.h>  
  7.   
  8. int main(int argc, char *argv[])  
  9. {  
  10.     fd_set rfds;  
  11.     struct timeval tv;  
  12.     int ret;  
  13.     int fd;  
  14.       
  15.     ret = mkfifo("test_fifo", 0666); // 创建有名管道  
  16.     if(ret != 0){  
  17.         perror("mkfifo:");  
  18.     }  
  19.       
  20.     fd = open("test_fifo", O_RDWR); // 读写方式打开管道  
  21.     if(fd < 0){  
  22.         perror("open fifo");  
  23.         return -1;  
  24.     }  
  25.       
  26.     ret = 0;  
  27.       
  28.     while(1){  
  29.         // 这部分内容,要放在while(1)里面  
  30.         FD_ZERO(&rfds);     // 清空  
  31.         FD_SET(0, &rfds);   // 标准输入描述符 0 加入集合  
  32.         FD_SET(fd, &rfds);  // 有名管道描述符 fd 加入集合  
  33.           
  34.         // 超时设置  
  35.         tv.tv_sec = 1;  
  36.         tv.tv_usec = 0;  
  37.           
  38.         // 监视并等待多个文件(标准输入,有名管道)描述符的属性变化(是否可读)  
  39.         // 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时  
  40.         // FD_SETSIZE 为 <sys/select.h> 的宏定义,值为 1024  
  41.         ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);  
  42.         //ret = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);  
  43.   
  44.         if(ret == -1){ // 出错  
  45.             perror("select()");  
  46.         }else if(ret > 0){ // 准备就绪的文件描述符  
  47.           
  48.             char buf[100] = {0};  
  49.             if( FD_ISSET(0, &rfds) ){ // 标准输入  
  50.                 read(0, buf, sizeof(buf));  
  51.                 printf("stdin buf = %s\n", buf);  
  52.                   
  53.             }else if( FD_ISSET(fd, &rfds) ){ // 有名管道  
  54.                 read(fd, buf, sizeof(buf));  
  55.                 printf("fifo buf = %s\n", buf);  
  56.             }  
  57.               
  58.         }else if(0 == ret){ // 超时  
  59.             printf("time out\n");  
  60.         }  
  61.       
  62.     }  
  63.       
  64.     return 0;  
  65. }  

poll:

[cpp]  view plain  copy
  1. #include <poll.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6. #include <unistd.h>  
  7.   
  8. int main(int argc, char *argv[])  
  9. {  
  10.   
  11.     int ret;  
  12.     int fd;  
  13.     struct pollfd fds[2]; // 监视文件描述符结构体,2 个元素  
  14.       
  15.     ret = mkfifo("test_fifo", 0666); // 创建有名管道  
  16.     if(ret != 0){  
  17.         perror("mkfifo:");  
  18.     }  
  19.       
  20.     fd = open("test_fifo", O_RDWR); // 读写方式打开管道  
  21.     if(fd < 0){  
  22.         perror("open fifo");  
  23.         return -1;  
  24.     }  
  25.       
  26.     ret = 0;  
  27.       
  28.     fds[0].fd = 0;   // 标准输入  
  29.     fds[1].fd = fd;  // 有名管道  
  30.           
  31.     fds[0].events = POLLIN; // 普通或优先级带数据可读  
  32.     fds[1].events = POLLIN; // 普通或优先级带数据可读  
  33.       
  34.     while(1){  
  35.       
  36.         // 监视并等待多个文件(标准输入,有名管道)描述符的属性变化(是否可读)  
  37.         // 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时  
  38.         ret = poll(fds, 2, -1);  
  39.         //ret = poll(&fd, 2, 1000);  
  40.   
  41.         if(ret == -1){ // 出错  
  42.             perror("poll()");  
  43.         }else if(ret > 0){ // 准备就绪的文件描述符  
  44.           
  45.             char buf[100] = {0};  
  46.             if( ( fds[0].revents & POLLIN ) ==  POLLIN ){ // 标准输入  
  47.                 read(0, buf, sizeof(buf));  
  48.                 printf("stdin buf = %s\n", buf);  
  49.                   
  50.             }else if( ( fds[1].revents & POLLIN ) ==  POLLIN ){ // 有名管道  
  51.                 read(fd, buf, sizeof(buf));  
  52.                 printf("fifo buf = %s\n", buf);  
  53.             }  
  54.               
  55.         }else if(0 == ret){ // 超时  
  56.             printf("time out\n");  
  57.         }  
  58.       
  59.     }  
  60.       
  61.     return 0;  
  62. }  

epoll:

[cpp]  view plain  copy
  1. #include <sys/epoll.h>  
  2. #include <stdio.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6. #include <unistd.h>  
  7. #include <stdlib.h>  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.   
  12.     int ret;  
  13.     int fd;  
  14.       
  15.     ret = mkfifo("test_fifo", 0666); // 创建有名管道  
  16.     if(ret != 0){  
  17.         perror("mkfifo:");  
  18.     }  
  19.       
  20.     fd = open("test_fifo", O_RDWR); // 读写方式打开管道  
  21.     if(fd < 0){  
  22.         perror("open fifo");  
  23.         return -1;  
  24.     }  
  25.       
  26.     ret = 0;  
  27.     struct epoll_event event;   // 告诉内核要监听什么事件  
  28.     struct epoll_event wait_event;  
  29.       
  30.       
  31.     int epfd = epoll_create(10); // 创建一个 epoll 的句柄,参数要大于 0, 没有太大意义  
  32.     if( -1 == epfd ){  
  33.         perror ("epoll_create");  
  34.         return -1;  
  35.     }  
  36.       
  37.     event.data.fd = 0;     // 标准输入  
  38.     event.events = EPOLLIN; // 表示对应的文件描述符可以读  
  39.       
  40.     // 事件注册函数,将标准输入描述符 0 加入监听事件  
  41.     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);  
  42.     if(-1 == ret){  
  43.         perror("epoll_ctl");  
  44.         return -1;  
  45.     }  
  46.       
  47.     event.data.fd = fd;     // 有名管道  
  48.     event.events = EPOLLIN; // 表示对应的文件描述符可以读  
  49.       
  50.     // 事件注册函数,将有名管道描述符 fd 加入监听事件  
  51.     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);  
  52.     if(-1 == ret){  
  53.         perror("epoll_ctl");  
  54.         return -1;  
  55.     }  
  56.       
  57.     ret = 0;  
  58.       
  59.     while(1){  
  60.           
  61.       
  62.         // 监视并等待多个文件(标准输入,有名管道)描述符的属性变化(是否可读)  
  63.         // 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时  
  64.         ret = epoll_wait(epfd, &wait_event, 2, -1);  
  65.         //ret = epoll_wait(epfd, &wait_event, 2, 1000);  
  66.           
  67.         if(ret == -1){ // 出错  
  68.             close(epfd);  
  69.             perror("epoll");  
  70.         }else if(ret > 0){ // 准备就绪的文件描述符  
  71.           
  72.             char buf[100] = {0};  
  73.               
  74.             if( ( 0 == wait_event.data.fd )   
  75.             && ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 标准输入  
  76.               
  77.                 read(0, buf, sizeof(buf));  
  78.                 printf("stdin buf = %s\n", buf);  
  79.                   
  80.             }else if( ( fd == wait_event.data.fd )   
  81.             && ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 有名管道  
  82.               
  83.                 read(fd, buf, sizeof(buf));  
  84.                 printf("fifo buf = %s\n", buf);  
  85.                   
  86.             }  
  87.               
  88.         }else if(0 == ret){ // 超时  
  89.             printf("time out\n");  
  90.         }  
  91.       
  92.     }  
  93.       
  94.     close(epfd);  
  95.       
  96.     return 0;  

select跨平台,一般平台都支持select。

select系统开销大,每次执行时都要将fd集合从内核态拷贝到用户态。

select单个进程能够监视的fd有限,一般是1024。

poll机制跟select类似,所以系统开销也大,但poll的fd(文件描述符)没有数量限制。

epoll监视的fd没有数量限制,epoll的fd拷贝只需要一次。

select、poll在等待和就绪时都需要遍历描述符,而epoll只需要在等待时遍历描述符并存储就绪的回调,唤醒时只需要查询就绪列表的回调并执行。epoll效率更高。

5、内存优化

首先,内存泄漏需要注意malloc、free的正确使用,保证分配多少释放多少,保证不要访问非法内存而导致崩溃,内核对象及时释放。这些由于编码导致的内存问题一般可以通过静态检测工具检测到。
其次,对一些难以查找的问题,可采用测试的方法。编写测试代码,测试部分模块,测试部分驱动,看是否是某个模块或驱动导致的内存问题。然后逐个排查问题。通过ls /proc/meminfo 、top、free或者一些工具可以查询到内存情况。也可以通过oops或系统log查询程序崩溃时的问题从而排查内存问题。
最后,对于应用程序崩溃的问题,可以用脚本编写守护进程,检测程序崩溃后重启程序。

猜你喜欢

转载自blog.csdn.net/guoyiyan1987/article/details/79014895
今日推荐