精华内容
下载资源
问答
  • Linux并发程序设计

    2019-09-03 23:21:11
    系统关闭时结束 Linux系统中大量使用, 很多服务程序以守护进程形式运行 守护进程特点 ---(前台进程可以在终端输入可以给终端输出,后台进程只能给终端输出) 始终在后台运行 独立于任何终端 周期性的执行某种任务...

     

    进程编程及守护进程

    进程控制块

          进程控制块(pcb)

             进程标识PID

             进程用户

             进程状态、优先级

             文件描述符表

                 寄存器 -----  pc:program counter(存放进程下一条指令的地址)

                                   栈:局部变量在栈上创建,在栈上被释放

    进程类型:

            交互进程:在shell下启动,可以在前台运行,也可以在后台运行

            批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行

            守护进程:和终端无关,一直在后台运行

    进程状态

            运行态:进程正在运行,或者准备运行

            等待态:进程在等待一个时间发生或某种系统资源

                可中断

                不可中断

           停止态:进程被中止,收到信号后可继续运行

           死亡态:已经终止的进程,但pcb没有被释放

    查看进程信息

          ps  查看系统进程快照   ps -ef(显示当前所有的进程信息)    ps  -ef|grep test(根据关键字查看进程)

          top  查看进程动态信息  

          /proc 查看进程的详细信息

    进程相关命令

         nice  按用户指定的优先级运行进程

         renice  改变正在运行进程的优先级

         jobs    查看后台进程

         bg     将挂起的进程在后台运行

         fg    把后台运行的进程放到前台运行

    进程创建  - fork

        #include<unisted.h>

        pid_t fork(void);

        创建新的进程,失败时返回-1

        成功时父进程返回子进程的进程号,子进程返回0

        通过fork的返回值区分父进程和子进程

    pid_t pid;
    
    if((pid = fork())<0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child process: my pid is %d\n",getpid());
    }
    else
    {
        printf("parent process :my pid is %d\n",getpid());
    }

    父子进程

         子进程继承了父进程的内容

         父子进程有独立的地址空间,互不影响

        若父进程先结束

             子进程成为孤儿进程,被init进程收养

             子进程变成后台进程

        若子进程先结束

              父进程如果没有及时回收,子进程变成僵尸进程

    进程结束   -exit/_exit

    #include<stdlib.h>

    #include<unisted.h>

    void exit(int status);

    void _exit(int status);

      结束当前的进程并将status返回

      exit结束进程时会刷新(流)缓冲区

    /*进程结束-exit-示例1*/
    #include<stdio.h>
    #include<stdlib.h>
    
    int main(void)
    {
        printf("this process will exit");//因为printf函数是一个标准的行缓冲,由于没有换行符(\n),只将字符放入标准输出流的缓冲上,不会显示到终端
        exit(0);//结束进程,并且刷新流,=此时会将标准输出流上的缓冲输出到终端
        printf("never be displayed");//因为进程已经结束,所以会打印该字符串
    }
    
    ./a.out
    this process will be exit 
    /*进程结束 - exit - 示例2*/
    
    #include<stdio.h>
    #include<stdlib.h>
    
    int main(void)
    {
        printf("using exit...\n");//因为有换行符,该字符被显示到终端上面
        printf("this is the end");//因为没有换行符,该字符只会被放入标准输出流的行缓冲中
        exit(0);//退出该进程,并且刷新流缓冲区,缓冲区内的字符会被显示到终端上面
    }
    
    
    ./a.out
    suing exit...
    this is the end
    如果将exit函数换为_exit函数,则只会结束进程,不会刷新流缓冲区

    进程 - exec函数族

       进程调用exec函数族执行某个程序

       进程当前内容被指定的程序替换

       实现让父子进程执行不同的程序

           父进程创建子进程

           子进程调用exec函数族

           父进程不受影响

    进程  - execl/execlp

    #include<unistd.h>

    int execl(const char *path,const char *arg, ...);

    int execlp(const char *file,const char *arg, ...);

      成功时执行制定的程序;失败时返回EOF

      path 执行的程序名称,包含路径

      arg... 传递给执行的程序的参数列表

      file  执行的程序的名称,在PATH中查找

     

    /*进程创建  - ececl(p) - 示例*/
    /*执行ls命令,显示/etc 目录下所有的文件的详细信息*/
    
    if(execl("/bin/ls" , "ls" , "-a" , "-l", "/ect",NULL)<0)
    
    {
        perror("execl");
    }
    
    if(execlp("ls","ls" , "-a" , "-l", "/ect",NULL)<0)
    {
        perror("execlp");
    }

    进程 - execv/execvp

    #include <unistd.h>

    int execv(const char *path,char *const argv[]);

    int execvp(const char *file,char *const argv[]);

    成功时执行制定的程序;失败时返回EOF

    arg... 封装成指针数组的形式

    /*进程创建 -execv(p) - 示例 */
    
    /*执行ls命令,显示/etc目录下所有文件的详细信息*/
    
    char *arg[] = {"ls","-a","-l","/etc",NULL};//字符指针数组
    
    if(execv("/bin/ls",arg)<0)
    {
        perror("execv");
    }
    
    if(execvp("ls",arg)<0)
    {
        perror("execvp");
    }

    进程 - system

    #include <stdlib.h>

    int system (const char *command);

    成功时返回命令command的返回值:失败时返回EOF

    当前进程等待command执行结束才继续执行

                                                                                                                                                        ------L5-D1-4


    进程回收

       子进程结束时由父进程回收

       孤儿进程由init进程回收

       若没有及时回收会出现僵尸进程

    进程回收 - wait

    #include <sys/types.h>

    #include <sys/wait.h>

    pid_t wait(int *status);  

         成功时返回回收的子进程的进程号;失败时返回EOF  

        若子进程没有结束,父进程一直阻塞

        若有多个子进程,哪个先结束就先回收  

         status 指定保存子进程返回值和结束方式的地址

        status为NULL表示直接释放子进程PCB,不接收返回值

    /*进程回收 – wait – 示例*/
    int status;
      pid_t pid;
    /*创建子进程*/
      if ((pid = fork()) < 0)
      {
         perror(“fork”);  exit(-1);
      }
    /*如果该进程为子进程*/
      else if (pid == 0) 
      {
         sleep(1);  exit(2);
      }
    /*父进程等待子进程结束,以16进制打印status*/
      else {
         wait(&status);  printf(“%x\n”, status);
      }  
    

    进程返回值和结束方式

    子进程通过020exit / _exit / return 返回某个值(0-255)

    父进程调用wait(&status) 回收  

    WIFEXITED(status)                            判断子进程是否正常结束

    WEXITSTATUS(status)                       获取子进程返回值

    WIFSIGNALED(status)                       判断子进程是否被信号结束

    WTERMSIG(status)                           获取结束子进程的信号类型

    进程回收 – waitpid

     #include  <unistd.h>  

    pid_t waitpid(pid_t pid, int *status, int option);      

    成功时返回回收的子进程的pid或0;失败时返回EOF  

    pid可用于指定回收哪个子进程或任意子进程  

    status指定用于保存子进程返回值和结束方式的地址  

    option指定回收方式,0 或 WNOHANG

    守护进程

        守护进程(Daemon)是Linux三种进程类型之一

       通常在系统启动时运行,系统关闭时结束 Linux系统中大量使用,

       很多服务程序以守护进程形式运行

    守护进程特点   ---(前台进程可以在终端输入可以给终端输出,后台进程只能给终端输出)

      始终在后台运行

      独立于任何终端

      周期性的执行某种任务或等待处理特定事件

    /*守护进程*/
    /*创建守护进程,每隔1秒将系统时间写入文件time.log*/
    
    int  main() {
        pid_t pid; //保存进程号
        FILE *fp;  //流指针
        time_t  t; //变量
        int  i;    
    /*创建子进程*/
        if ((pid = fork()) < 0) {
          perror(“fork”);  exit(-1);
        }
    /*父进程退出,子进程被init收养,在后台运行*/
        else if (pid > 0) {
           exit(0);
        }
        setsid();   //子进程创建新的会话,脱离原先的终端
        umask(0);    //重设权限掩码为0,创建一个新的文件中指定的权限会与权限掩码的反进行与操作生成该进程的文件权限,该权限掩码设为0 ,则不影响当前进程的权限设定
        chdir(“/tmp”);//修改当前工作目录,指向根目录下temp
        for (i=0; i< getdtablesize(); i++) {
           close(i);
        }//将从父进程继承来的文件都关闭
        if ((fp = fopen(“time.log”, “a”)) == NULL) {
           perror(“fopen”); exit(-1); }//打开文件
        /*将本地时间写入文件中*/
        while  ( 1 ) {
           time(&t);
           fprintf(fp, “%s”, ctime(&t));
           fflush(fp);//刷新流
           sleep(1);
        }
    

                                                                                                                                                               L5-D2-2


    ​​​​专题专题

    进程

        进程有独立的地址空间

        Linux为每个进程创建task_struct

        每个进程都参与内核调度,互不影响

    线程

       进程在切换时系统开销大

       很多操作系统引入了轻量级进程LWP

       同一进程中的线程共享相同地址空间 Linux不区分进程、线程

    线程特点

      通常线程指的是共享相同地址    

    空间的多个任务(使用多线程的好处:大大提高了任务切换的效率 避免了额外的TLB & cache的刷新)

    线程创建 – pthread_create

    #include  <pthread.h>

     int  pthread_create(pthread_t *thread, const        

      pthread_attr_t *attr, void *(*routine)(void *), void *arg);  

          成功返回0,失败时返回错误码  

         thread 线程对象  

          attr 线程属性,NULL代表默认属性  

          routine 线程执行的函数  

         arg 传递给routine的参数

    线程回收 – pthread_join

      #include  <pthread.h>  

      int  pthread_join(pthread_t thread, void **retval);          

          成功返回0,失败时返回错误码  

          thread 要回收的线程对象  

           调用线程阻塞直到thread结束  

          *retval 接收线程thread的返回值

    线程结束 – pthread_exit

      #include  <pthread.h>

      void  pthread_exit(void *retval);          

           结束当前线程  

           retval可被其他线程通过pthread_join获取

           线程私有资源被释放

    /*线程 - 示例*/
    
    char message[32] = “Hello World”;//全局变量在静态存储区,可以被所有的线程访问
      void *thread_func(void *arg);
    
      int main(void) {
         pthread_t  a_thread;//定义一个线程对象变量
         void *result;
    /*创建线程a_thread,用的是缺省属性,指定thread_func函数,给函数传0值*/
         if (pthread_create(&a_thread, NULL, thread_func, NULL) != 0) {
            printf(“fail to pthread_create\n”);  exit(-1);
         }
         pthread_join(a_thread, &result);//等待a_thread线程结束,回收线程,并且接收返回值
         printf(“result  is  %s\n”, result);//回收成功后,将a_thread中的返回值打印
         printf(“message  is  %s\n”, message);//并将数组中的内容也打印出来
         return 0;
      }
    /*线程主函数*/
    void  *thread_func(void *arg) {
          sleep(1);
          strcpy(message, “marked  by  thread”);//将数组中的字符串进行修改
          pthread_exit(“thank you for waiting for me”);//结束当前先线程,并返回当前字符串的首地址
       }
    
    
    
    
       $ gcc  -o  test  test.c  -lpthread
       $ ./test
        result  is  thank you for waiting for me
        message  is  marked  by  thread
    

    线程间通信

        线程共享同一进程的地址空间

        优点:线程间通信很容易  

         通过全局变量交换数据

         缺点:多个线程访问共享数据时需要同步或互斥机制

    线程通信--同步

        同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事情

        由信号量来决定线程是继续运行还是阻塞、等待

    /*线程同步 – 示例1*/
    /*两个线程同步读写缓冲区(生产者/消费者问题)*/
    
    
     // 此处省略若干头文件
      char buf[32];
      sem_t  sem;//定义一个全局的信号量对象
      void *function(void *arg);//声明线程要执行的函数
      int main(void) {
         pthread_t  a_thread;
    /*信号量初始化,sem为信号量对象的地址,信号量用于进程内部线程通信,第三个参数表示信号量为0*/
         if (sem_init(&sem, 0,  0) < 0) {
            perror(“sem_init”);  exit(-1);
         }
    /*创建线程,线程地址为a_thread,用缺省的属性,线程的函数为function,传递参数为空*/
         if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
            printf(“fail to pthread_create\n”);  exit(-1); 
        }
    /*写线程*/
    printf(“input ‘quit’ to exit\n”);
           do {
              fgets(buf, 32, stdin);//从标准输入读取一串字符串到buf里面
              sem_post(&sem); //执行V操作,释放资源
            }  while (strncmp(buf, “quit”, 4) != 0);//比较字符串
            return 0;
       }
    /*读线程*/  
       void  *function(void *arg) {
           while ( 1 ) {
              sem_wait(&sem);//执行P操作,申请资源
              printf(“you enter %d characters\n”, strlen(buf));//申请资源成功,显示当前缓冲区内字符长度是多少
           } 
       }
    
    /*线程同步 – 示例2*/
    /*两个线程同步读写缓冲区(生产者/消费者问题)*/
    
    
     char buf[32];
      sem_t  sem_r, sem_w;//定义全局可写信号量和可读信号量
      void  *function(void *arg);
      
      int main(void) {
         pthread_t  a_thread;
    /*两个信号量初始化,因为一开始,buf缓冲区为0,可读信号量为0,可写信号量为1*/
         if (sem_init(&sem_r, 0, 0) < 0) {
            perror(“sem_init”);  exit(-1);  
         }
         if (sem_init(&sem_w, 0,1) < 0) {
            perror(“sem_init”);  exit(-1);  
         }
    /*创建一个新的线程*/
    if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
               printf(“fail to pthread_create\n”);  exit(-1); }
    
           printf(“input ‘quit’ to exit\n”);
           do {
              sem_wait(&sem_w);//可写信号量P操作,申请资源----判断当前有没有可写的缓冲区
              fgets(buf, 32, stdin);---写的动作产生了一个可读的资源
              sem_post(&sem_r); //可读信号量V操作,释放资源---唤醒读线程
            }  while (strncmp(buf, “quit”, 4) != 0);
           
            return 0;
       }
    void  *function(void *arg) {
           while ( 1 ) {
              sem_wait(&sem_r);//可读信号量P操作,申请资源---检查当前缓冲区有没有可读的资源
              printf(“you enter %d characters\n”, strlen(buf));//对缓冲区内容处理后,缓冲区又变为空闲
              sem_post(&sem_w);//可写信号量V操作,释放资源---唤醒写线程
           } 
       }
    

    线程通信 – 互斥

      临界资源  

         一次只允许一个任务(进程、线程)访问的共享资源

      临界区

         访问临界区的代码

      互斥机制

          mutex互斥锁

          任务访问临界资源前申请锁,访问完后释放锁

    /*线程互斥 – 示例*/
    
    
    // 此处省略若干头文件
      unsigned int count, value1, value2;
      pthread_mutex_t lock;//定义一个全局的互斥锁
    
      void *function(void *arg);
      int main(void) {
         pthread_t  a_thread;
    /*对互斥锁进行初始化,锁的地址是lock,锁的属性是NULL,用默认的*/
         if (pthread_mutex_init(&lock, NULL) != 0) {
            printf(“fail to pthread_mutex_init\n”);  exit(-1);
         }
    /*创建线程,用a_thread对象来创建,默认属性,线程函数名为function,传入空地址的参数*/
         if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
            printf(“fail to pthread_create”);  exit(-1); 
         }
    /*主线程*/
      while ( 1 ) {
            count++;
    /*条件编译,根据条件是否用锁---如果_LOCK_有宏定义,则用互斥锁,然后把count赋值给value1和value2,如果_LOCK_没有宏定义,则不用互斥锁*/
    #ifdef  _LOCK_
            pthread_mutex_lock(&lock);//申请锁
    #endif
            value1 = count;
            value2 = count;
    #ifdef  _LOCK_
            pthread_mutex_unlock(&lock);//释放锁
    #endif
         }
         return 0;
      }
    /*另外一个线程*/
     void *function(void *arg) {
         while ( 1 ) {
    #ifdef  _LOCK_
            pthread_mutex_lock(&lock);//申请锁
    #endif
            if (value1 != value2) {
                printf(“value1 = %u, value2 = %u\n”, value1, value2);
                usleep(100000);
            }
    #ifdef  _LOCK_
            pthread_mutex_unlock(&lock);//释放锁
    #endif
         }
         return  NULL;  }
           
    
    
    
    /*运行结果*/
    不使用互斥锁(程序中不定义宏)
        $  gcc  –o  test  test.c  -lpthread
         $ ./test
         value1 = 19821, value2 = 19820
         value1 = 14553456, value2 = 14553455
         value1 =  29196032, value2 = 29196031
          ……
    使用互斥锁(–D_LOCK_    向程序中传递_LOCK_,相当于程序中定义了这个宏)
         $ gcc  –o  test  test.c  –lpthread  –D_LOCK_
         $ ./test
    

                                                                                                                                                    L5 - D3 - 4


    Unix进程间通信

    早期UNIX进程间通信方式

       无名管道(pipe)

       有名管道 (fifo)

       信号(signal)

    System V IPC

       共享内存(share memory)

       消息队列(message queue)

       信号灯集(semaphore set)  

    套接字(socket)

    无名管道具有如下特点:

        只能用于具有亲缘关系的进程之间的通信

        单工的通信模式,具有固定的读端和写端

        无名管道创建时会返回两个文件描述符,分别用于读写管道

    /*无名管道 – 示例*/
    /*子进程1和子进程2分别往管道中写入字符串;父进程读管道内容并打印*/
    
    
    #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <string.h>
      #include <sys/types.h>
      #include <sys/wait.h>
      int main(void) {
         pid_t pid1, pid2;//定义变量,保存fork的返回值
         char buf[32];
         int pfd[2];//定义数组,数组用来创建管道
         /*创建无名管道*/
    if (pipe(pfd) < 0) {
            perror(“pipe”); exit(-1);
         }
        /*创建子进程,一定要在无名管道创建后进行子进程创建,否则子进程不会继承父进程的打开的文件及代码*/
    if ((pid1 = fork()) < 0) {
            perror(“fork”);  exit(-1);
         }
         else if (pid1 == 0) {          //  子进程1
            strcpy(buf, “I’m process1”);//将字符串写入buf中
            write(pfd[1], buf, 32);//将buf中的数据写入到无名管道中
            exit(0);   
         }
         else {             //  父进程
        /*创建第二个子进程*/
            if ((pid2 = fork()) < 0) {
               perror(“fork”); exit(-1);
            }
            else if (pid2 == 0) {        //  子进程2
               sleep(1);
               strcpy(buf, “I’m process 2”);
               write(pfd[1], buf, 32);
            }  
            else {        //  父进程
               wait(NULL);//等待回收任意一个子进程
               read(pfd[0], buf, 32);将pdf管道中的数据读入到buf中
               printf(“%s\n”, buf);
               wait(NULL);
               read(pfd[0], buf, 32);
               printf(“%s\n”, buf);
            }
         }
         return  0;
      }       
    

    读无名管道

        写端存在

        有数据            read返回实际读取的字节数

        无数据            进程读阻塞

        写端不存在

         有数据            read返回实际读取的字节数

         无数据            read返回0

    写无名管道

          读端存在

            有空间            write返回实际写入的字节数 

            无空间            进程写阻塞

    /*获取无名管道的大小,有读端的情况*/
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc,  char *argv[])
    {
       int pfd[2], count = 0;
       char buf[1024];
       /*创建管道*/
       if (pipe(pfd) < 0)
       {
         perror("pipe");
         exit(-1);	 
       }
       
       while ( 1 )
       {
          write(pfd[1], buf, 1024);
    	  printf("wrote %dk bytes\n", ++count);//打印目前写入字节数
       }
       
       return  0;
     }
    
    /*验证在无进程时,写进程写入数据,管道断裂(进程被信号结束)*/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int main(int argc,  char *argv[])
    {
       int pfd[2];
       int status;
       pid_t pid;
    
       if (pipe(pfd) < 0)   // 创建管道
       {
         perror("pipe");
    	 exit(-1);
       }
       close(pfd[0]);   // 关闭管道读端
       
       if ((pid = fork()) < 0)   // 创建子进程
       {
         perror("fork");
    	 exit(-1);
       }
       else if (pid == 0)
       {
         write(pfd[1], "data", 4);  // 写管道
    	 printf("wrote data to pipe\n");
    	 exit(0);
       }
       else
       {
         wait(&status);  // 回收子进程,将返回值写入status中
    	 printf("%x\n", status);   // 打印子进程退出状态
       }
       
       return  0;
    }
    

                                                                                                                                                                 2019.9.7


    有名管道特点

          对应管道文件,可用于任意进程之间进行通信

          打开管道时可指定读写方式

          通过文件IO操作,内容存放在内存中

    有名管道创建 – mkfifo

        #include <sys/types.h>

        #include <sys/stat.h>

        int  mkfifo(const char *path, mode_t mode);

          成功时返回0,失败时返回EOF

          path  创建的管道文件路径

          mode 管道文件的权限,如0666

    /*有名管道读写 – 示例*/
    
    /*进程A:循环从键盘输入并写入有名管道myfifo,输入quit时退出
    进程B:循环统计进程A每次写入myfifo的字符串的长度*/
    
    
    /*  create_fifo.c  */
    // 省略头文件
      int main(void) {
    /*创建有名管道名称为myfifo,权限为0666*/
         if(mkfifo(“myfifo”, 0666) < 0) {
              perror(“mkfifo”);
              exit(-1);
         }
         return 0;
      }
    
    
    /*  write_fifo.c  */
    // 省略头文件
      #define   N 32
      int main(void) {
         char buf[N];
         int pfd;
    /*以只写方式打开管道*/
         if ((pfd = open(“myfifo”, O_WRONLY)) < 0) {
            perror(“open”);  exit(-1);
         }
         while ( 1 ) {
            fgets(buf, N, stdin);//从标准输入读取字符串到buf中
            if (strcmp(buf, “quit\n”) == 0) break;  
            write(pfd, buf, N);把buf中所有的内容写入管道中。
       }
         close(pfd);//关闭打开的管道
    
         return 0;
      }
    
    
    
    /*  read_fifo.c  */
    // 省略头文件
      #define   N 32
      int main(void) {
         char buf[N];
         int pfd;
    /*以只读方式打开管道*/
         if ((pfd = open(“myfifo”, O_RDONLY)) < 0) {
            perror(“open”); 
            exit(-1);
         }
    /*从管道中读取字符串到buf中,当管道中没有数据时,读进程阻塞*/
         while (read(pfd, buf, N) > 0) {
            printf(“the length of string is %d\n”, strlen(buf));
         }
         close(pfd);
     
         return 0;
      }
    
    
    
    无论单独打开读端或者写端,程序都会阻塞。
    

    信号机制

      信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

      linux内核通过信号通知用户进程,不同的信号类型代表不同的事件

      Linux对早期的unix信号机制进行了扩展

      进程对信号有不同的响应方式  

         缺省方式  

         忽略信号  

         捕捉信号

    信号相关命令 kill / killall

      kill [-signal] pid

         默认发送SIGTERM

         -sig 可指定信号

        pid  指定发送对象

     killall [-u  user | prog]

       prog  指定进程名  

       user  指定用户名

    信号发送 – kill / raise

       #include  <unistd.h>

       #include <sys/types.h>  

       #include <signal.h>  

       int  kill(pid_t pid,  int sig);  

       int  raise(int sig);//只能给自己发信号  

            成功时返回0,失败时返回EOF  

            pid 接收进程的进程号;0代表同组进程; -1代表所有进程  

            sig 信号类型

    信号相关函数 – alarm / pause

       int  alarm(unsigned int seconds);  

          成功时返回上个定时器的剩余时间,失败时返回EOF  

          seconds 定时器的时间  

           一个进程中只能设定一个定时器,时间到时产生SIGALRM

       int pause(void);  

           进程一直阻塞,直到被信号中断  

           被信号中断后返回-1,errno为EINTR

    /*信号函数 alarm / pause – 示例*/
    
    
    #include <stdio.h>  
    #include <unistd.h>
    
    int main() {
       alarm(3);//创建了一个闹钟,指定3s钟后,时间到
       pause();//让进程睡眠,3s钟后,闹钟时间到。内核会向进程发送一个定时器信号,收到信号后,进程会首先响应这个定时器信号sigalarm,默认终止进程,不会执行下面的语句。
       printf(“I have been waken up!\n”);
       return 0;
    }
    
    
    
    $ ./a.out 
    Alarm clock
    重要:alarm经常用于实现超时检测     
    

    设置信号响应方式 – signal

     #include  <unistd.h>

     #include <signal.h>  

     void (*signal(int signo, void (*handler)(int)))(int);  

        成功时返回原先的信号处理函数,失败时返回SIG_ERR  

        signo 要设置的信号类型  

        handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;  

    信号函数 signal – 示例
    
    
    // 头文件省略
    /*信号处理函数的接口,根据信号处理函数中形参的值来区分不同的信号*/
    void handler (int signo) {
         if (signo == SIGINT) {
            printf(“I have got SIGINT!\n”); }
         if (signo == SIGQUIT) {
            printf(“I have got SIGQUIT\n”); }
    }
    
    int  main() {
    /*设置信号的响应方式,不同的信号对应一样的处理函数*/
         signal(SIGINT, handler);
         signal(SIGQUIT, handler);
         while ( 1 ) pause();//阻塞到该处,直到其他信号结束当前进程
         return 0;
    }
    

                                                                                                                                                      L5 - D5 - 3


    System V IPC

        IPC 对象包含: 共享内存、消息队列和信号灯集

        每个IPC对象有唯一的ID(只有创建ipc对象的进程知道)

         IPC对象创建后一直存在,直到被显式地删除

         每个IPC对象有一个关联的KEY(指定)

          ipcs (显示系统中当前所有的ipc对象)/ ipcrm(删除)

    System V IPC – ftok

      #include  <sys/types.h>  

      #include <sys/ipc.h>  

      key_t  ftok(const char *path,  int proj_id);  

        成功时返回合法的key值,失败时返回EOF  

        path  存在且可访问的文件的路径  

        proj_id  用于生成key的数字,不能为0

    /*System V IPC - ftok – 示例*/
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>  
    #include <sys/ipc.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[]) {
       key_t key;//key_t类型等价于一个整形
    /*调用ftok生成key,第一个参数点是路径,相当于当前目录,第二个参数是工程号,传的是一个字符常量*/
       if ((key = ftok(“.”, ‘a’)) == -1) {
          perror(“key”);
          exit(-1);
       }   
    ……
    
    
    //每一个进程都得生成相同的key,这样才能通过相同的key找到对应的ipc对象

    共享内存

       共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝

       共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活

       由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用

    共享内存使用步骤

       创建/打开共享内存

       映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

       读写共享内存

       撤销共享内存映射

       删除共享内存对象

    /*共享内存创建 - shmget – 示例1*/
    
    /*要求:创建一个私有的共享内存,大小为512字节,权限为0666*/
     
    
      int shmid;
    /*创建私有共享内存,共享内存一定是新建的,因此第三个参数不用加IPC_CREAT*/
      if ((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0) {
         perror(“shmget”);
         exit(-1);
      }
    
    
    
    /*共享内存创建 - shmget – 示例2*/
    
    /*要求:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666*/
    
    
    
      key_t key;
      int shmid;
    
      if ((key = ftok(“.”, ‘m’)) == -1) {
         perror(“ftok”);
         exit(-1);
      }
      if ((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0) {
         perror(“shmget”);
         exit(-1);
      }
    

    共享内存映射 – shmat

        #include <sys/types.h>

        #include <sys/shm.h>  

        void  *shmat(int shmid, const void *shmaddr, int shmflg);  

           成功时返回映射后的地址,失败时返回(void *)-1  

           shmid   要映射的共享内存id  

           shmaddr   映射后的地址, NULL表示由系统自动映射  

           shmflg   标志位  0表示可读写;SHM_RDONLY表示只读

    /*通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型*/
    
       /* 例如:在共享内存中存放键盘输入的字符串 */
    
    
    
          char *addr;
          int  shmid;
          ……
    /*进行共享内存的映射,一个参数是id,第二个是映射后的地址,Null表示系统自动映射,第三个参数表示可读写*/
          if ((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) {
             perror(“shmat”);
             exit(-1);
          }
          fgets(addr, N, stdin);//往共享内存存放一个键盘输入的字符串,第一个参数是要存放输入缓冲区内容的首地址
          ……
    

    消息队列

        消息队列是System V IPC对象的一种

       消息队列由消息队列ID来唯一标识

       消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等

       消息队列可以按照类型来发送/接收消息

    消息队列使用步骤

        打开/创建消息队列   msgget

        向消息队列发送消息   msgsnd

        从消息队列接收消息   msgrcv

        控制消息队列   msgctl

    /*消息队列创建/打开  -  示例*/
    
    
    
    ……
    int main() {
       int msgid;//定义消息变量的ID
       key_t key;
    //生成key,第一个参数时当前目录,第二个参数时指定的工程号
       if ((key = ftok(“.”, ‘q’)) == -1) {
          perror(“ftok”);  exit(-1);
       }
    /*创建消息队列*/
       if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) {
          perror(“msgget”); exit(-1);
       }
       …… 
       return 0;
    }
    

    消息格式

       通信双方首先定义好统一的消息格式

       用户根据应用需求定义结构体类型

       首成员类型为long,代表消息类型(正整数)

       其他成员都属于消息正文

    /*消息发送  -  示例*/
    
    
    /*定义消息格式,第一个成员代表消息的类型(正整数),第二个成员是字符数组用来存放消息的字符串*/
    typedef  struct {
       long mtype;
       char mtext[64];
    } MSG;
    #define  LEN  (sizeof(MSG) - sizeof(long))//(正文的长度)定义一个宏,结构体大小减去第一个成员的大小
    int main() {
       MSG buf;
       ……
       buf.mtype = 100; //填充消息的类型为
       fgets(buf.mtext, 64, stdin);//将要发送消息的正文从键盘输入到buf的消息成员mtext中
       msgsnd(msgid, &buf,LEN, 0);//发送消息到消息队列中,第一个参数是消息队列的id,第二个参数是消息缓冲区的首地址,LEN代表消息正文长度,0表示成功才返回
       …… 
       return 0;
    }
    
    /*消息接收  -  示例*/
    
    
    
    typedef  struct {
       long mtype;
       char mtext[64];
    } MSG;
    #define  LEN  (sizeof(MSG) - sizeof(long))
    int main() {
       MSG buf;
       ……
     /*
      消息接收:成功时返回收到的消息长度,失败时返回-1
              msgid   消息队列id
              buf   消息缓冲区地址
              LEN   指定接收的消息长度 
              200   指定接收的消息类型   
             msgflg   标志位   0 或 IPC_NOWAIT
     */
       if (msgrcv(msgid, &buf,LEN, 200, 0) < 0) {
          perror(“msgrcv”);
          exit(-1);
       }
       ……
    }
    
    
    /*要求:两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接收并打印对方发送的消息*/
    
    /*clientA.c*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h> 
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    typedef  struct {
       long mtype;
       char mtext[64];
    } MSG;
    
    #define  LEN  (sizeof(MSG) - sizeof(long))
    #define  TypeA  100
    #define  TypeB  200
    
    int main() 
    {
      int msgid;
      key_t key;
      MSG buf;
      /*生成key*/
      if ((key = ftok(".", 'q')) == -1) 
      {
        perror("ftok");  
    	exit(-1);
      }
      /*创建消息队列*/
      if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) 
      {
        perror("msgget"); 
    	exit(-1);
      }
      
      while ( 1 )
      {
        buf.mtype = TypeB;
        printf("input > ");	//打印提示信息
        fgets(buf.mtext, 64, stdin);//从键盘输入消息到消息队列中
        msgsnd(msgid, &buf, LEN, 0);//发送消息
    	if (strcmp(buf.mtext, "quit\n") == 0) //如果键盘输入quit,则退出当前进程
        {
            msgctl(msgid, IPC_RMID, NULL);//删除消息队列
            exit(0);
        }
        msgrcv(msgid, &buf, LEN, TypeA, 0);//接收消息队列
    	if (strcmp(buf.mtext, "quit\n") == 0) break;//如果接收到quit,跳出循环
    	printf("recv message : %s", buf.mtext);//打印接收消息
      }
      
      msgctl(msgid, IPC_RMID, NULL);//删除消息队列
      return 0;
    }
      
    
    /*clientB.c*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h> 
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    typedef  struct {
       long mtype;
       char mtext[64];
    } MSG;
    
    #define  LEN  (sizeof(MSG) - sizeof(long))
    #define  TypeA  100
    #define  TypeB  200
    
    int main() 
    {
      int msgid;
      key_t key;
      MSG buf;
      /*生成key*/
      if ((key = ftok(".", 'q')) == -1) 
      {
        perror("ftok");  
    	exit(-1);
      }
      /*创建消息队列*/
      if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) 
      {
        perror("msgget"); 
    	exit(-1);
      }
      
      while ( 1 )
      {
        msgrcv(msgid, &buf, LEN, TypeB, 0);//接收消息
    	if (strcmp(buf.mtext, "quit\n") == 0) break;//如果接受到quit,则退出当前进程
    	printf("recv message : %s", buf.mtext);//打印接收消息
        buf.mtype = TypeA;//发送消息类型为TypeA
        printf("input > ");	//打印提示信息
        fgets(buf.mtext, 64, stdin);//从键盘输入消息到消息队列中
        msgsnd(msgid, &buf, LEN, 0);//发送消息
    	if (strcmp(buf.mtext, "quit\n") == 0) exit(0);//如果发送的消息是quit,则退出当前进程 
      }
      msgctl(msgid, IPC_RMID, NULL);//删除当前消息队列
      
      return 0;
    }
      
    
    
    

                                                                                                                                                    L5 - D6 - 4


    System V IPC - 信号灯

      信号灯也叫信号量,用于进程/线程同步或互斥的机制

      信号灯的类型  

          Posix 无名信号灯  

          Posix有名信号灯  

          System V  信号灯

      信号灯的含义  

           计数信号灯

    System V IPC - 信号灯特点

          System V 信号灯是一个或多个计数信号灯的集合

          可同时操作集合中的多个信号灯

          申请多个资源时避免死锁

    System V信号灯使用步骤

        打开/创建信号灯   semget

        信号灯初始化   semctl

        P/V操作   semop

        删除信号灯  semctl

    /*信号灯集初始化 -  示例*/
    
    
    
    /*要求:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0*/
    
    
    union  semun  myun;//定义一个共用体变量
    
      myun.val = 2;//初值放入共用体成员val中
    /*
        成功时返回0,失败时返回EOF
        semid    要操作的信号灯集id
        0        要操作的集合中的信号灯编号
       SETVAL    命令字
        myun     存放初始化的值
    
    */
      if (semctl(semid, 0, SETVAL, myun) < 0) {
         perror(“semctl”);     exit(-1);
      }
      myun.val = 0;
      if (semctl(semid, 1, SETVAL, myun) < 0) {
         perror(“semctl”);     exit(-1);
      }
    
    /*信号灯集/共享内存 -  示例*/
    
    /*
    要求:父子进程通过System V信号灯同步对共享内存的读写
    
    父进程从键盘输入字符串到共享内存
    
    子进程删除字符串中的空格并打印
    
    父进程输入quit后删除共享内存和信号灯集,程序结束
    */
    
    
    #include<stdio.h>
    #include<singal.h>
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/shm.h>
    #include<sys/sem.h>
    
    #define N 64   // 缓冲区的大小
    #define READ 0 //信号灯集合的编号
    #define WRITE 1
    
    union semun
    {
        int val;
        struct semid_ds *buf;
        unsigned short *array;
        struct seminfo *_buf;
    }
    
    
    void init_sem(int semid, int s[], int n)
    {
        int i;
        union semun myun;
        for(i=0;i<n,i++)
        {
            myun.val = s[i];
            semctl(semid, i, SETVAL, myun);
        }
    }
    
    void pv(int semid, int num, int op)
    {
        struct sembuf buf;
           
        buf.sem_num = num;
        buf.sem_op = op;
        buf.sem_flg = 0;
        semop(semid, &buf, 1);
    }
    
    int main()
    {
        int shmid, semid ,s[]={0,1};//共享内存的ID,信号灯的ID,信号灯的初始化数值
        pid_t pid;
        key_t key;
        char *shmaddr;
    
        /*生成key*/
        if((key = ftok(".",'s')) == -1)
        {
            perror("ftok");
            exit(-1);
        }
    
        /*创建共享内存*/
        if((shmid = shmget(key, N, IPC_CREAT|0666)) < 0)
        {
    
            perror("shmget");
            exit(-1);
        }
    
        /*创建信号灯集合*/
        if((semid = semget(key, 2, IPC_CREAT|0666)) < 0)
        {
            PERROR("semget");
            goto _error1;
        }
    
        /*信号灯集合的初始化*/
        init_sem(semid, s, 2);
    
        /*映射共享内存*/
        if ((shmaddr = (char *)shmat(shmid, NULL, 0)) == (char *)-1)
        {
            perror("shamt");
            goto _error2;
        }
        
        /创建子进程/
        if((pid = fork()) < 0)
        {
            perror("fork");
            goto _error2;
        }
        elseif (pid == 0)//子进程:删除空格,打印信息
        {
            char *p, *q;
            while(1)
            {
                pv(semid, READ, -1);
                p = q = shmaddr;
                while( *q )
                {
                    if(*q!=' ')
                    {
                        *p++ = *q;       
                    }
                    q++;
                } 
                *p = '\0';
                printf("%s",shmaddr);
                pv(semid, WRITE, 1);
            
            }
        }
        else//父进程:循环输入
        {
            while(1)
            {
                pv(semid, WRITE, -1);
                printf("input > ");
                fgets(shmaddr, N, stdin);
                if(strcmp(shmaddr, "quit\n") == 0) break;
                pv(semid, READ, 1);
            }
            kill(pid, SIGUSR1);
        }
    
    _error2:
        semctl(semid, 0, IPC_RMID),如果映射共享内存失败,删除创建的信号灯集
    
    _error1:
        shmctl(shmid, IPC_RMID, NULL);//如果创建信号灯失败,则删除共享内存
    
        return 0;
    }

     

    展开全文
  • Linux并发程序设计习题 1、路由器是根据哪一层的信息为数据包选择路由 ( C ) A 物理层 B 数据链路层 C 网络层 D 传输层 2、只用于同一主机内部进程间通信的socket应使用的协议族是 ( B ) A AF_INET B AF_UNIX C AF_...

    Linux并发程序设计习题

    1、路由器是根据哪一层的信息为数据包选择路由 ( C )
    A 物理层
    B 数据链路层
    C 网络层
    D 传输层

    2、只用于同一主机内部进程间通信的socket应使用的协议族是 ( B )
    A AF_INET
    B AF_UNIX
    C AF_NS
    D AF_IMPLINK

    3、在下列功能中,哪一个最好地描述了OSI(开放系统互连)模型的数据链路层( B )
    A 保证数据正确的顺序、无错和完整
    B 处理信号通过介质的传输
    C 提供用户与网络的接口
    D 控制报文通过网络的路由选择

    4、某一网络子网掩码为255.255.255.248,则该网络能连接( C )台主机。
    A 255台
    B 16台
    C 6台
    D 8台

    5、网络层传输的数据单位为( C )
    A 原始比特流
    B 帧
    C 分组
    D 字节

    6、下面说法错误的是( C)
    A accept函数连接建立成功会返回一个连接套接字
    B listen函数会把普通套接字编程监听套接字
    C TCP网络通信编程中不能使用sendto函数
    D TCP网络通信中在close函数和接受函数之间进行四次挥手

    7、猜牌问题S先生、P先生、Q先生他们知道桌子的抽屉里有16张扑克牌:红桃A、Q、4黑桃J、8、4、2、7、3草花K、Q、5、4、6方块A、5。约翰教授从这16张牌中挑出一张牌来,并把这张牌的点数告诉P先生,把这张牌的花色告诉Q先生。这时,约翰教授问P先生和Q先生:你们能从已知的点数或花色中推知这张牌是什么牌吗?于是,S先生听到如下的对话:P先生:我不知道这张牌。Q先生:我知道你不知道这张牌。P先生:现在我知道这张牌了。Q先生:我也知道了。听罢以上的对话,S先生想了一想之后,就正确地推出这张牌是什么牌。请问:这张牌是什么牌?(D )
    A 红桃A
    B 黑桃4
    C 草花Q
    D 方块5

    8、简述TCP和 UDP的异同点

    相同点:都是传输层协议
    不同点:tcp协议面向连接,提供可靠的传输;udp协议无连接,不保证可靠的传输
    评分:共5点,每点2分
    

    9、简述UNIX/Linux下主要的四种IO模型的特点

    (1)阻塞式IO   :最简单、最常用;效率低
    (2)非阻塞式IO :可以处理多路IO;需要轮询,浪费CPU资源
    (3)IO多路复用 :同时出路多路IO且不需要轮询
    (4)信号驱动IO :异步通知模式,需要底层驱动的支持
    评分:共4点,每点2.5分。
    

    10、简述网络超时检测的三种方法。

    (1)通过设置socket的属性,一次设置,管终身。代码如下:
    
            struct timeval tv;
    
            tv.tv_sec = 5;
    
            tv.tv_usec = 0;
    
            setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); //设置接收超时
    
            recv() / recvfrom();        //从socket读取数据
    (2)设置select函数的最后的参数是特定值,每次调用都需要重新赋值。
    (3)设置定时器(timer), 捕捉SIGALRM信号
    评分:每个点3分,全写对加1分。
    

    11、写一段socket程序,实现服务器和客户端TCP/IP通信,编写客户端向服务器发送“HELLO”,服务器端IP地址为192.168.0.7 端口号是6000

    服务器端:
    Socket函数---1分
    地址填充---1分 
    Bind函数---1分
    Listen函数---1分
    accept函数---1分
     
    客户端:
    Socket函数---1分
    地址填充---1分 
    Listen函数---1分
    Connect函数---1分
    发送信息---1分
    

    12、TCP三次握手,通讯双方是为了同步什么信息?

    (1)客户端bai发送一个带duSYN标志的TCP报文到服务器。zhi这是三次dao握手过程中的zhuan报文1。
    
    (2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
    
    (3) 客户必须再次回应服务段一个ACK报文,这是报文段3。
    
    展开全文
  • Linux并发程序设计(1)——进程

    千次阅读 多人点赞 2020-08-07 17:47:42
    在实际设计程序中,可能这个程序不是单独的完成一个单一的功能,而是同时完成多个任务。例如QQ登录以后可以同时聊天、下载文件、听音乐等。要完成这么一个多任务的程序那么我们就需要会多进程、多线程相关知识来完成...

            在实际设计程序中,可能这个程序不是单独的完成一个单一的功能,而是同时完成多个任务。例如QQ登录以后可以同时聊天、下载文件、听音乐等。要完成这么一个多任务的程序那么我们就需要会多进程、多线程相关知识来完成一个多任务程序的设计。
    往期文章推荐:
           Shell函数的作用以及变量的作用域
           史上最全的Linux命令汇总(超全面!超详细!)收藏这一篇就够了!
    Alt

    进程的概念

           在了解进程之前我们需要了解什么是程序,程序就是存放在磁盘上的指令和数据的有序集合(文件),程序是静态的(程序编译好以后指令、数据等就不能再改变)。
           进程与程序是密切相关的,进程是操作系统为了执行一个程序分配资源的总称、是程序的一次执行过程,进程相当于程序来说是动态的,包括创建、调度、执行和消亡。当系统执行一个程序时,至少创建了一个进程

    进程内容

    在这里插入图片描述
           进程主要包括代码(正文)、用户程序、系统数据。系统数据有助于操作系统管理进程。系统数据段主要包括进程控制块(PCB Process Control Block)、CPU寄存器值、堆栈。

    系统数据段 作用
    进程控制块(pcb) ① 进程标识PID ② 进程用户 ③ 进程状态、优先级 ④文件描述符表
    寄存器 程序计数器PC(program counter)该寄存器保存了程序下一个指令的地址
    堆栈 C语言局部变量、函数的参数、返回值都是在栈上创建的

    进程类型

    • 交互进程:在Shell下启动。以在前台运行,也可以在后台运行。(后台运行给指令后加一个‘&’)
    • 批处理进程:与在终端无关,被提交到一个作业队列中以便执行
    • 守护进程:和终端没有关系,一直在后台运行

    进程的状态

    • 运行态:进程正在运行,或者准备运行
    • 等待态:进程在等待一个事件的发生某种系统资源(分为可中断和不可中断)
    • 停止态:进程被终止,收到信号后可继续运行
    • 死亡态:已终止的程序,但是pcb没有被释放
    • 进程状态图:

    Linux下进程的相关命令

    查看进程相关信息
    终端命令 显示结果
    ps -ef 进程所有者、进程号、父进程号、进程执行时间、程序对应名称
    ps aux 相对于ps -ef还能够显示进程的当前状态(运行态、停止态、前台进程等)
    top 可以查看进程动态信息(每隔3秒钟统计系统信息)能显示系统最消耗CPU资源的进程
    /proc目录查看 进入该目录下可以查看进程的详细信息

    注意使用ps命令都可以配合管道命令进行筛选相关进程信息

    修改进程优先级
    命令 作用
    nice 按用户指定优先级运行进程
    renice 改变正在运行进程的优先级

    示例:

    nice -n 2 ./test#将test进程的nice值设置为2
    
    renice -n 2 29070#将进程号为29070的nice值设置为2
    

    注意进程的nice值默认是0,范围为- 20~20,nice值越小则优先级越高。普通用户设置的nice值只能为0 ~ 20,且只能增加nice值

    前后台进程切换
    命令 作用
    jobs 查看后台进程(作业)
    bg 将挂起的进程在后台运行
    fg 把后台运行的进程放到前台运行

    进程相关函数

    创建进程——fork

    #include <unistd.h>
    pid_t fork(void);//pid_t 等价与有符号整型
    
    • 创建新的进程,失败返回-1
    • 创建成功时,父进程返回子进程的进程号,子进程返回0
    • 通过fork()的返回值来区分父子进程

    示例:

    #include<stdio.h>
    #include<unistd.h>
    
    int main()
    {
        pid_t pid;
        if((pid=fork())<0){
    	perror("fork");//创建失败,打印错误信息
    	return -1;
        }
        else if(pid==0){
    	printf("child prosess:my pid is %d\n",getpid());//创建子进程,返回子进程的pid号
        }
        else{
    	printf("parent process:my pid is %d\n",getpid());//打印父进程的进程号
        }
    }
    
    父子进程
    • 子进程继承了父进程的内容(几乎赋值了父进程的全部内容,但是pid、ppid不同)
    • 父子进程有独立的地址空间,互不影响
    • 若父进程先结束
      • 子进程成为孤儿进程,被init进程收养
      • 子进程变成后台进程
    • 若子进程先结束
      • 父进程如果没有及时回收,子进程变成僵尸进程

    注意:
           1. 子进程是从fork函数的下一条语句开始执行,并没有执行fork
           2. 内核先调用父进程就先执行父进程,先调用子进程就先执行子进程
           3. 父进程可以多次调用fork,子进程也可以调用fork创建孙进程(注意进程的回收)

    结束进程——exit/_exit/return

    #include <stdlib.h>
    #include <unistd.h>
    void exit(int status);//用stdlib.h头文件
    void _exit(int status);//用unistd.h头文件
    
    • 结束当前的进程并将status返回
    • exit结束后会刷新流的缓冲区
      示例:
    #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {
    	printf("this process will exit");//没有换行符只能写到标准输出流的缓冲区,不会在终端显示
    	exit(0);//结束进程,刷新缓冲区的流,因此上条语句会显示在终端上
    	printf("never be discovered");//不会被打印	
    }
    

    exec函数族

    作用

    • 进程调用exec函数族执行某个程序
    • 进程当前内容被指定程序替换
    • 实现父子进程执行不同程序
      • 父进程创建子进程
      • 父进程调用exec函数族
      • 父进程不受影响
    #include <unistd.h>
    int execl(const char *path,const *arg,...);//arg...传递给执行的程序的参数列表
    int execlp(const char *file,const char *arg,...);//arg...传递给执行的程序的参数列表
    
    int execv(const char *path,char *const argv[]);//arg...封装成指针数组的形式
    int execvp(const char*file,char *const argv[]);//arg...封装成指针数组的形式 
    
    • 成功时返回指定程序;失败时返回EOF
    • path执行的程序名称,包含路径
    • file执行程序的名称,在PATH中查找
    示例:
           执行ls命令,显示/etc目录下所有文件的详细信息
    方式1:execl
    if(execl("/bin/ls","ls","-a","-l","/etc",NULL)<0{//一定要以NULL结尾,并判断函数是否执行成功
    	perror("execl");
    }	
    
    方式2:execlp
    if(execl("ls","ls","-a","-l","/etc",NULL)<0{//会自动在PATH路劲搜索
    	perror("execl");
    }	
    方式3:execv
    char *arg[]={"ls","-a","-l","/etc",NULL};//将要传递的参数放在数组中
    if(execv("/bin/ls",arg)<0){
    	perror("execv");
    }
    方式3:execvp
    if(execvp("ls",arg)<0){
    	perror("execvp");//会自动在PATH路径中搜索
    }
    
    system函数
    #include <stdlib.h>
    int system(const char *command)
    
    • 成功时返回command的返回值;失败时返回EOF(自动创建一个子进程,子进程执行命令)
    • 当前进程(父进程)等待command执行结束后才继续执行。

    进程回收——wait/waitpid

    • 子进程结束时由父进程回收
    • 孤儿进程由init进程回收
    • 若孤儿进程没有回收会出现僵尸进程

    回收函数——wait:

    #include <unistd.h>
    pid_t wait(int *status);
    
    • 成功时返回回收子进程的进程号;失败时返回EOF
    • 若子进程没有结束,父进程一直阻塞
    • 若有多个子进程,那个进程先结束就先回收
    • status指定保存子进程返回值和结束方式的地址
    • status为NULL表示直接释放子进程PCB,不接收返回值

    示例:

    #include<stdio.h>
    #include<unistd.h>
    
    int main()
    {
        int status;//接收返回值以及返回方式
        pid_t pid;//接收fork的返回值
        if((pid=fork())<0){
            perror("fork");
            exit(-1);
        }
        else if(pid==0){
            sleep(1);//如果是子进程睡眠一秒
            exit(2);//退出子进程
        }
        else{
            wait(&status);//等待子进程结束
            printf("%x\n",status);//打印输出结果
        }
    }
    
    • 子进程通过exit/_exit/return返回某个值(0~255)
    • 父进程通过调用wait(&status)回收
    宏标识符 作用
    WIFEXITED(status) 判断子进程是否正常结束
    WEXITSTATUS(status) 获得子进程的返回值
    WIFSIGNALED(status) 判断子进程是否被信号结束
    WTERMSIG(status) 获取结束子进程的信号类型

    回收函数——wait_pid:

    #include<unistd.h>
    pid_t waitpid(pid_t pid,int status,int option);
    
    • 成功时返回回收子进程的pid或0;失败(如:没有子线程)返回EOF
    • pid可用于指定回收那个子进程或任意子进程
    • status指定用于保存子进程的返回值和结束方式的地址
    • option指定回收方式,0(阻塞的方式,等待子进程结束)或WNOHANG(非阻塞)

    示例:

    waitpid(pid,&status,0);//指定进程的进程号,阻塞方式
    waitpid(pid,&status,WNOHABG);//子进程结束返回子进程的进程号,没有结束返回0
    waitpid(-1,&status,0);//-1表示回收任意一个子进程,等价于wait
    waitpid(-1,&status,WNOHANG);//非阻塞的方式回收任意进程
    

    守护进程

           守护进程(Daemon)是Linux三种类型之一,通常在系统启动时运行,系统结束时关闭,Linux系统中大量使用,很多服务程序就是以守护进程形式运行。
           Linux以会话(session)、进程组的方式管理进程,每个进程就属于一个进程组。而会话是一个或者多个进程组的集合。通常用户打开一个终端时,系统会创建一个临时会话,所有通过该终端运行的进程都属于这个会话。终端关闭时,所有进程会被结束。那么我们要使得守护进程与终端无关,当终端关闭的时候守护进程依旧可以运行。

    守护进程特点

    • 始终在后台运行
    • 独立于任何终端
    • 周期性的执行某种任务或等待处理特定事件

    守护进程的创建

    1. 创建子进程,父进程退出

    if(fork()>0){
    	exit(0);
    }
    
    • 子进程变成孤儿进程,被init进程收养
    • 子进程在后台运行,但是依旧和终端相关联

    2. 子进程创建新会话

    if(setsid()<0)//通过setsid创建一个新的会话
    {
    	exit(-1);
    }
    
    • 子进程成为新的会话组长
    • 子进程脱离原先的终端

    3. 更改当前工作目录

    chdir("/");
    chdir("/tmp");//所有用户可读可写可执行
    
    • 守护进程一直在后台运行,其工作目录不能被卸载
    • 重新设定当前工作目录cwd

    4. 重设文件权限掩码

    if(umask(0)<0){
    	exit(-1);
    }
    
    • 文件权限掩码设置为0
    • 只影响当前进程

    5. 关闭打开的文件描述符

    int i;
    for(i=0;i<getdtablesize();i++)//文件描述符最小就是0。getdtablesize()返回当前进程打开最大文件数
    {
    	close(i);
    }
    
    • 关闭所有从父进程继承的打开文件
    • 已脱离终端,stdin/stdout/stderr无法再使用

    示例:
           创建守护进程,没隔1秒将系统时间写入文件time.log

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <unistd.h>
    #include <time.h>
    
    int main()
    {
    	pid_t pid;
    	FILE *fp;
    	time_t t;
    	int i;
    
    	if((pid = fork())< 0)
    	{
    		perror("fork");
    		exit(-1);
    	}
    	if(pid > 0)
    	{
    		exit(0);
    	}
    	else if(pid == 0)
    	{
    		if(setsid()< 0)//创建一个新的会话
    		{
    			perror("setsod");
    			exit(-1);
    		}
    		else
    		{
    			chdir("/tmp");//tmp目录可读可写可执行
    			if(umask(0) < 0)//重设文件权限掩码
    			{
    				exit(-1);
    			}
    			else
    			{
    				for(i=0;i<getdtablesize();i++)
    				{
    					close(i);
    				}
    			}
    			while(1)
    			{
    				fp = fopen("time.log","a+");
    				time(&t);
    				fprintf(fp,"%s",ctime(&t));
    				fflush(fp);
    				sleep(1);
    			}
    		}
    	}
    }
    

           不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
           如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
    在这里插入图片描述

    展开全文
  • 线程1 线程简介(1)线程的特点:(2)线程资源1)共享资源2)私有资源2 Linux线程库3 线程创建 - pthread_create4 线程回收 - pthread_join5 线程结束 - pthread_exit6 线程实例 1 线程简介 首先进程有如下特点: ...

    1 线程简介

    首先进程有如下特点:

    • 有独立的地址空间;
    • 每个进程创建task_struct;
    • 都参与内核调度,互不影响。
      缺点是,切换时系统开销大。
      下图是一个进程读取数据的流程在这里插入图片描述
      CPU先获取cache,读指令,访问数据,若cache中无数据,再从内存中加载一批数据。

    (1)线程的特点:

    • 是轻量级进程(LWP);
    • 同一进程中的线程共享相同的地址空间;
    • LInux不区分进程线程;
    • 进程独占一个空间,线程共享地址空间;
      根据线程的特点,多线程有以下优势
    • 大大提高了任务切换的效率;
    • 避免额外的tlb和cache刷新。

    (2)线程资源

    1)共享资源

    • 可执行指令;
    • 静态数据;(全局变量,字符串变量等);
    • 进程中打开的文件描述符;
    • 当前工作目录;
    • 用户ID;
    • 用户组ID。

    2)私有资源

    • 线程ID(TID);
    • PC(程序计数器)和相关寄存器(可执行独立代码);
    • 堆栈(局部变量);
    • 错误号(errno);
    • 优先级;
    • 执行状态和属性。

    2 Linux线程库

    pthread线程库提供了如下基本操作

    • 创建线程
    • 回收线程
    • 结束线程

    同步和互斥机制

    • 信号量
    • 互斥锁

    3 线程创建 - pthread_create

    #include <pthread.h>
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
    void *(*routine)(void *), void *arg);

    • 成功返回0,失败返回错误码;
    • thread 线程对象;
    • attr 线程属性,NULL代表默认属性;
    • routine线程执行的函数;
    • arg 传递给routine的参数。

    4 线程回收 - pthread_join

    #include <pthread.h>
    int pthread_join (pthread_t thread, void **retval);

    • 成功返回0,失败返回错误码;
    • thread是指要回收的线程对象;
    • 调用线程阻塞直到thread结束;
    • *retval 一级指针传地址,接收线程thread的返回值。

    5 线程结束 - pthread_exit

    #include <pthread.h>
    void pthread_exit(void *retval);

    • 相当于return,即结束当前线程;
    • retval可被其他线程通过pthread_join获取;
    • 线程私有资源被释放;
    • 地址不能是线程战中创建的地址,线程结束后所有资源被释放,包括线程栈。

    6 线程实例

    char message[32] = "hello world"; //定义全局字符串数组,在静态存储区,
    									//可以被所有线程访问
    void *thread_func(void *arg);		//线程要执行函数
    
    int main(void){
    	pthread_t a_thread;				//线程对象变量
    	void *result					//接收返回值
    	
    	if( pthread_create(&a_thread, NULL, thread_func, NULL) != 0){
    		printf("fail to pthread_create");
    		exit(-1);
    	}
    
    	pthread_join(&a_thread, &result);
    	printf("result is %s\n", result);
    	printf("message is %s\n", message);
    	
    	return 0;
    }
    
    void thread_func(void *arg){
    	sleep(1);
    	strcpy(message, "marked by thread");
    	pthread_exit("thank you for waiting for me");
    }
    

    执行指令
    $ gcc -o test test.c -l pthread
    $./test
    结果:
    thank you for waiting for me
    masked by thread

    展开全文
  • Linux并发程序设计(2)——线程

    千次阅读 多人点赞 2020-08-10 10:29:19
    在某个程序运行的同时系统就会创建一个进程,并且系统会给进程分配独立的地址空间,而且系统会把进程的详细信息保存在task_struct结构体中。由于每个进程都要参与内核调度互相不影响,那么会导致进程在切换时系统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,302
精华内容 520
关键字:

linux并发程序设计

linux 订阅