Skip to content

NAMZseng/c-practice-under-linux

Repository files navigation

C-Practice-Under-Linux

Linux下的C语言开发学习

相关笔记

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);
       }
       

About

Linux下的C语言开发学习

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published