美文网首页
Linux下的C语言开发学习笔记

Linux下的C语言开发学习笔记

作者: Anan_楠 | 来源:发表于2019-12-13 20:54 被阅读0次

    个人GitHub代码链接:https://github.com/NAMZseng/C-Practice-Under-Linux


    gcc编译四个过程

    • 预处理

      • 头文件包含、宏替换、条件编译、删除注释

      • gcc -E

      • 该步骤生成的文件后缀 .i

    • 编译

      • 语法检查、将预处理的文件编译成汇编文件

      • gcc -S

      • 该步骤生成的文件后缀 .s

    • 汇编

      • 将汇编文件处理成二进制文件

      • gcc -c

      • 该步骤生成的文件后缀 .o

    • 链接

      • 整合二进制文件、相关库函数、启动代码生成可执行文件,main函数是由启动代码调用的,程序是从启动代码开始运行的。

      • 静态链接,指调用ld/collect2链接程序,将所有的.o中的机器指令整合到一起,然后保存到可执行文件中。

      • 动态链接,在编译的时候只留下调用接口(函数第一条指令的地址),当程序真正运行的时候,才去链接执行,动态链接这件事不是在编译时发生的,是在程序动态运行时发生。

      • 比如程序中调用printf函数,这个函数基本都是动态库提供的,程序编译后代码里面是没有printf函数代码的,只有printf这个接口,当程序运行起来后,再去动态链接printf所在的动态库,那么程序就能调用printf函数。

      • Linux默认的动态库搜索路径/usr/lib

      • gcc

      • 该步骤生成的文件后缀 .out

      参考自

    动态空间的申请与释放

     //动态从堆区空间申请
     int *p = (int *)malloc(n*sizeof(int)); 
     if(p == NULL) 
     {
      perror("malloc"); 
      exit(-1);
     }
     // 空间的释放
     if(p != NULL)
     {
      // 释放指向的内存空间
      free(p);
      // 取消对以释放空间的指向
      p = NULL;
     }
    

    进程相关

    进程创建

    • 进程是系统资源分配的最小单位,是正在运行的,且占有内存空间。

    • Linux环境中创建进程可通过调用fork() / vfork()函数,在一个已经存在的进程中创建一个新的子进程,它拷贝了父进程的地址空间,包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等,子进程仅独有其进程号、计时器等,因此使用fork()创建的进程的代价是较大的。

    • 父子进程的地址空间相同,但相互独立。

    • 子进程从fork()语句后开始执行。

    • 但在进程中使用exec函数族时,exec中调用执行的程序会替换当前进程。

       #include <sys/types.h>
       #include <unistd.h>
    
       ...
    
       pid_t pid = fork();
       if(pid < 0) // 创建失败
       {
           perror("fork");
           _exit(-1);
       }
       if(pid == 0) // 子进程
       {
          ...
       }
       else if (pid > 0) // 父进程,返回的pid是子进程的id
       {
          ...
       }
    

    进程通信——管道

    • 管道是内核提供的一段内存(队列),这段内存抽象成文件,通过访问文件描述符的形式,来读写这块内存中的数据。

    • 单向通信,半双工

    • 面向字节流

    • 内置同步互斥机制。互斥:当多个进程一起读时会,读到完整数据或者读不到数据。同步:管道为空,读阻塞;管道满了,写阻塞。

    • 所有引用这个管道的进程都销毁,管道才释放。真正释放的只是管道在内核中对应的这段内存,这个内存才是管道的本质

    • 无名管道

      • 仅限于具有“血缘关系”的进程间(如父子进程)的通信,因为这类进程在创建时拷贝了父进程的地址空间,其中包含了以打开的文件描述符,故可以打开以在父进程中创建的无名管道,进行相互通信。
      // 创建数组存储管道的文件描述符,fd[0]用于读, fd[1]用于写
       int fd[2];
       // 创建并打开管道
       pipe(fd);
    
       pid_t pid = fork();
    
       if(pid == 0) 
       {
       // 可在子进程中读取管道信息
       char buf[128] = "";
       read(fd[0], buf, sizeof(buf));
       }
       if(pid > 0)
       {
       // 可在父进程中向管道写信息
       write(fd[1], "hello pipe", strlen("hello pipe"));
       }
    
    • 命名管道

      • 相互通信的进程可通过管道的名字(即文件名),打开管道进行读/写

      • 一般在读端与写端都分别创建同一命名管道(当要创建文件存在时,文件不会重复再创建),因为无法确定实际运行中,哪端的代码先运行。若仅在一端创建管道文件,而是另一端先运行到open管道文件的代码时,会因找不到该文件而报错。通过读写两端分别创建同一命名管道文件,可以确保无论哪端先执行到open代码,都能正常打开管道。

      • 读端进程

         // 创建一个有名管道,并赋予相关权限
          mkfifo("fifo_demo", 0777);
        
          // 以只读的方式打开创建的有名管道
          int fd = open("fifo_demo", O_RDONLY);
        
          char buf[128] = "";
          read(fd, buf, sizeof(buf));
          printf("读取到的数据为:%s\n", buf);
        
          // 关闭文件描述符fd
          close(fd);
        
      • 写端进程

         // 创建一个有名管道
         mkfifo("fifo_demo", 0777);
      
         // 以只写的方式打开创建的有名管道
         int fd = open("fifo_demo", O_WRONLY);
      
         // 发送消息
         write(fd, "hello fifo", strlen("hello fifo"));
      
         // 关闭文件描述符fd
         close(fd);
      
      • 文件描述符符重定向
         if(pid == 0) // 子进程
          {
           // 由于grep仅从输入设备0中读取信息
           // 所以需要将文件描述符1重定向到fd[0],使grep从无名管道中读取结果
           dup2(fd[0], 0);
          ​
           // 执行grep命令
           execlp("grep", "grep", "ps", NULL);
          }
          else if (pid > 0) // 父进程
          {
           // 由于ps仅向输出设备1中写结果
           // 所以需要将文件描述符1重定向到fd[1],使ps向无名管道中输出结果
           dup2(fd[1], 1);
          ​
           // 执行ps命令
           execlp("ps", "ps", "-elf", NULL);
          }
      

    相关文章

      网友评论

          本文标题:Linux下的C语言开发学习笔记

          本文链接:https://www.haomeiwen.com/subject/vfsqnctx.html