分类 linux 下的文章

linux编程学习 02 进程与信号

进程

  • 程序:是存放在磁盘文件中的可执行文件
  • 进程

    • 程序的执行示例被称为进程(process)
    • 进程独立的权限和职责,如果系统中某个进程崩溃,它不会影响其余的进程。
    • 每个进程允许在气各自的虚拟地址空间中,进程之间可以通过由内核控制的机制互相通讯
  • 进程ID

    • 每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID),进程ID总是一个非负整数
  • 每一个启动的进程都对应一个task_struct结构体
  • task_struct可以在/usr/src/kernels/2.6.32-642.13.1.el6.x86_64/include/linux/sched.h文件中查看,注意只有安装了kernel-devel包才有这个文件

c程序启动过程

  • 内核启动特殊例程
  • 启动例程

    • 在进程的main函数执行之前内核会启动
    • 该历程放置在/lib/libc.so.*
    • 编译器在编译时会将启动例程编译进可执行文件中
  • 启动例程作用

    • 搜集命令行的参数传递给main函数中的argc和argv
    • 搜集环境信息构建环境表并传递给main函数
    • 登记进程的终止函数

进程终止

  • 正常终止

    • 从main函数返回
    • 调用exit(标准c库函数)
    • 调用_exit_Exit(系统调用)
    • 最后一个线程从其启动例程返回
    • 最后一个线程调用pthread_exit
  • 异常终止

    • 调用abort
    • 接受到一个信号并终止
    • 最后一个线程对取消请求做处理响应
  • 进程返回

    • 通常程序运行成功返回0,否则返回非0
    • 在shell中可以查看进程返回值(echo $?)

atexit函数

int atexit(void (*function)(void));

  • 返回:若成功则为0,若出错则为-1
  • 功能:像内核登记终止函数
  • 头文件:
#include <stdlib.h>
  • 每个启动的进程都默认登记了一个标准的终止函数
  • 终止函数在进程终止时释放进程所占用的一些资源
  • 登记的多个终止函数执行顺序是以栈的方式执行,先登记的后执行
  • 使用_exit或_Exit退出时,不会执行终止函数

进程终止方式的区别

对比项returnexit()_exit()/_Exit()
是否刷新标准I/O缓存
是否自动调用终止函数

在centos 6.6 中,使用 _exit和_Exit会调用终止函数

ps命令

  • 可以查看到:进程的id,进程的用户id,进程状态和进程的command
  • ps aux可以查看到更多的信息
  • ps输出信息
    |列名|解释|
USER进程的属主
PID进程的ID
PPID父进程ID
%CPU进程占用的CPU时间
%MEM占用内存的百分比
NI进程的NICE值,数值大,表示较少占用CPU时间
VSZ进程的虚拟大小
RSS驻留集的大小,可以理解为内存中页的数量
TTY终端ID
WCHAN正在等待的进程资源
START启动进程的时间
TIME进程消耗CPU的时间
COMMAND命令的名称和参数

进程状态

进程常见状态

  • 运行状态

    • 系统当前进程
    • 就绪状态进程
    • ps命令的STAT列为R
  • 等待状态

    • 等待事件发生
    • 等待系统资源
    • 等待状态可分为可中断等待和不可中断等待
    • 可中断等待时ps命令的STAT列为S
    • 不可中断等待时ps命令的STAT列为D
  • 停止状态

    • ps命令的STAT列为T
  • 僵尸状态

    • 进程终止或结束
    • 在进程表项中仍有记录
    • ps命令的STAT列为Z

进程调度

  • 第一步:处理内核中的工作
  • 第二步:处理当前进程
  • 第三步:选择进程

    • 实时进程
    • 普通进程
  • 第四步:进程交换
  • task_struct中的调度信息

    • 策略

      • 轮流策略
      • 先进先出策略
    • 优先权

      • Jiffies变量
    • 实时优先权

      • 实时进程之间
    • 计数器

进程标识

pid_t getpid(void); 获得当前进程ID
pid_t getuid(void); 获得当前进程的实际用户ID
pid_t geteuid(void); 获得当前进程的有效用户ID,启动程序时使用的用户id,参照nginx
pid_t getgid(void); 获得当前进程的用户组ID
pid_t getppid(void); 获得当前进程的父进程ID
pid_t getpgrp(void); 获得当前进程所在进程组ID
pid_t getpgid(pid_t pid); 获得进程ID为pid的进程所在的进程组ID

  • 头文件
#include <unistd.h>
#include <sys/types.h>

进程创建

pid_t fork(void);
pid_t vfork(void);

  • 返回:子进程中为0,父进程中为子进程ID,出错为-1
  • fork创建的新进程被称为子进程,该函数被调用一次,但返回两次。两次返回的区别是:在子进程中的返回值是0,而在父进程中的返回值则是进进程的进程ID
  • 创建子进程,父子进程哪个先运行根据系统调度且复制父进程的内存空间
  • vfork创建子进程,但子进程先运行且不复制父进程的内存空间

子进程的继承

  • 子进程的继承属性

    • 用户的信息和权限,目录信息,信号信息,环境,共享存储段,资源限制,堆,栈和数据段,就是把父进程的信息复制一遍。但是共享代码段(复制虚拟地址,但是虚拟地址映射到同一个物理地址)。
  • 子进程特有属性

    • 进程ID,锁信息,运行时间,未决信号
  • 操作文件时的内核结构变化

    • 子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node。
    • 父进程创建一个子进程后,文件表项的引用计数器加1变2,当父进程作close操作后,计数器减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项
  • 实例
/**
 * 如何创建子进程
 * */
void fork_example1(void)
{
        int pid = fork();
        if(pid == -1) 
        {   
                printf("fork error\n");
        }   
        // 根据fork的返回值判断是父进程还是子进程
        if(pid > 0)
        {   
                // 父进程睡眠下,如果父进程在子进程结束之前结束,子进程ppid就为1
                sleep(1);
                printf("I am parent process,my pid is %d,my ppid is %d\n",getpid(),getppid());
        }
        else
        {   
                printf("I am child process,my pid is %d,my ppid is %d\n",getpid(),getppid());
        }           
}
/**
 * 父子进程哪个先运行由系统调度
 **/
void fork_example2(void)
{
        int pid = fork(),l = 10;
        if(pid == 0)
        {
                for(int i = 0;i < l;i++)
                {
                        sleep(1);
                        printf("parent pid => %d,ppid => %d;count => %d\n",getpid(),getppid(),i);
                }
        }
        else
        {
                for(int i = 0;i < l;i++)
                {
                        sleep(1);
                        printf("child pid => %d,ppid => %d;count => %d\n",getpid(),getppid(),i);
                }
        }
}
int a = 1;
/**
 * 子进程和父进程的虚拟地址是一样的,下面的打印可以看出,但是物理地址是不一样的
 * */
void fork_example3(void)
{
        int b = 2;
        int pid = fork();
        if(pid > 0)
        {
                printf("&a = %p;&b = %p",&a,&b);
        }
        else if(pid == 0)
        {
                printf("&a = %p;&b = %p",&a,&b);
        }
}
/**
 * io
 * */
void fork_example4(void)
{
        FILE* f1 = fopen("f1.txt","w+");
        int f2 = open("f2.txt",O_CREAT | O_WRONLY);
        // c标准函数写,这里的start在文件里面会写入两次,因为子进程会把标准的id的缓存也复制一边>,最后结束时,就会写入两次
        fprintf(f1,"start\n");
        // 系统函数写
        write(f2,"start\n",6);
        int pid = fork();
        if(pid > 0)
        {
                // c标准函数写
                fprintf(f1,"parent end\n");
                // 系统函数写
                write(f2,"parent end\n",11);
        }
        else if(pid == 0)
        {
                // c标准函数写
                fprintf(f1,"child end\n");
                // 系统函数写
                write(f2,"child end\n",10);
        }
        fclose(f1);
        close(f2);
}
void fork_example5(void)
{
        int f = open("a.txt",O_WRONLY | O_CREAT);
        int pid = fork();
        if(pid > 0)
        {
                // 主进程调整位置,子进程写入
                lseek(f,0,SEEK_END);
        }
        else
        {
                sleep(3);
                // f是主进程f的复制,所以主进程使用lseek对子进程也是有效的
                write(f,"write",5);
        }
        close(f);
}

进程链

int main(void)
{
        int pid = 0;
        for(int i = 10;i > 0;i--)
        {
                if(pid > 0)
                {
                        sleep(i);
                        printf("pid = %d;ppid = %d\n",getpid(),getppid());
                        break;
                }
                else if(pid == 0)
                {
                        pid = fork();
                }
        }
        return 0;
}

进程扇

int main(void)
{
        int pid = 0;
        for(int i = 10;i > 0;i--)
        {       
                pid = fork();
                if(pid == 0)
                {       
                        printf("pid = %d;ppid = %d\n",getpid(),getppid());
                        return 0;
                }
        }
        sleep(3);
        printf("pid = %d;ppid = %d\n",getpid(),getppid());
        return 0;
}

守护进程

  • 守护进程(deamon)是生存期长的一种进程。他们常常在系统引导装入时启动,在系统关闭时终止。
  • 所有守护进程都以超级用户(用户id为0)的优先权运行
  • 守护进程没有控制终端
  • 守护进程的父进程都是init进程

孤儿进程

  • 父进程结束,子进程就成为孤儿进程,会由1号进程(init进程)领养。

僵尸进程

  • 子进程结束但是没有完全释放内存(在内核中的task_struct没有释放),该进程就成为僵尸进程。
  • 当僵尸进程的父进程结束后就会被init进程领养。最终被回收
  • 避免僵尸进程

    • 让僵尸进程的父进程来回收,父进程每隔一段时间来查询子进程是否结束并回收,调用wait()或waitpid(),通知内存释放僵尸进程
    • 采用信号SIGCHLD通知处理,并在信号处理程序中调用wait函数
    • 让僵尸进程成为孤儿进程,有init回收
  • 实例
int main(void)
{
        int pid = fork();
        if(pid == 0)
        {
                printf("child finish\n");
                // 这里return之后子进程就成为僵尸进程了
                return 0;
        }
        while(1)
        {
                sleep(1);
        }
        return 0;
}

wait和waitpid

pid_t wait(int* status);

  • 返回:成功返回子进程id,出错返回-1
  • 功能:等待子进程推出并回收,防止僵尸进程的产生
    pid_t waitpid(pid_t pid,int* status,int options);
  • 返回:成功返回子进程id,出错返回-1
  • 功能:wait函数的非阻塞版本
  • 头文件
#include <sys/types.h>
#include <sys/wait.h>
  • wait与waitpid函数区别

    • 在一个子进程终止前,wait使其调用者阻塞
    • waitpid有一选择项,可使调用者不阻塞
    • waitpid等待一个指定的子进程,而wait则等待所有的子进程,返回任一终止子进程的状态
  • status参数

    • 为空时,代表任意状态结束的子进程,若不为空,则代表指定状态结束的子进程
  • 检查wait和waitpi函数返回终止状态的宏

    • WIFEXITED/WEXITSTATUS(status)

      • 若为正常终止子进程返回的状态,则为真
    • WIFSIGNALED/WTERMSIG(status)

      • 若为异常终止子进程返回的状态则为真(接到一个不能捕捉的信号)
    • WIFSTOPPED/WSTOPSIG(status)

      • 若为当前暂停子进程的返回的状态,则为真
  • options参数

    • WNOHANG

      • 若由pid指定的进程没有退出则立即返回,则waaitpid不阻塞,此时其返回值为0
    • WUNTRACED

      • 若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态
  • waitpid函数的pid参数

    • pid == -1

      • 等待任一子进程,功能与wait等效
    • pid > 0

      • 等待进程id与pid相等的子进程
    • pid == 0

      • 等待其组id等于调用进程的组id的任一子进程
    • pid < -1

      • 等待其组id等于pid的绝对值得任一进程
  • 示例
void out_put(int status)
{       
        int i = 0;
        if(WIFEXITED(status))
        {       
                printf("normal exit;");
                i = WEXITSTATUS(status);
        }
        else if(WIFSIGNALED(status))
        {       
                printf("abnormal exit;");
                i = WTERMSIG(status);
        }
        else if(WIFSTOPPED(status))
        {       
                printf("stopped sig;");
                i = WSTOPSIG(status);
        }
        printf("status = %d\n",i);
}
int main(void)
{
        int pid = fork();
        int status;
        if(pid == 0)
        {
                printf("pid = %d;ppid = %d\n",getpid(),getppid());
                sleep(3);
                // 暂停
                pause();
                exit(1);
                return 11;
        }
        // 会阻塞
        // wait(&status);
        // sleep(3);
        // waitpid(pid,&status,WNOHANG);
        // 不会阻塞,
        while(0 == waitpid(pid,&status,WUNTRACED | WNOHANG))
        {

        }
        out_put(status);
        return 0;
}

exec函数

  • 在用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序
  • 当进程调用一冲exec函数时,该程序完全由新程序代换,替换原有进程的正文,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程id并不改变。exec只是用另一个新程序替换了当前进程的正文,数据,堆和栈。
int execl(const char* pathname,const char* arg0,.../* (char*)0 */);
int execv(const char* pathname,char* const argv[]);
int execle(const char* pathname,const char* arg0,.../* (char*)0,char* const envp[] */);
int execve(const char* pathname,char* const argv[],char* const envp[]);
int execlp(const char* pathname,const char* arg0,.../* (char*)0 */);
int execvp(const char* pathname,char* const argv[]);
  • 返回:出错返回-1,成功则不返回
  • exec系列函数注意点

    • execve函数为系统调用,其余为库函数。执行execve函数后面的代码不执行
    • execlp和execvp函数中的pathname,相对路径和绝对路径均可使用,其他四个函数中的pathname只能使用绝对路径。相对路径一定要在进程环境表对应的PATH中
    • argv参数为新程序执行main函数中传递argv参数,最后一个元素为NULL
    • envp为进程的环境表
  • 六个函数都是以“exec”四个字母开头,后面的字幕代表了其用法上的区别:

    • 带有字母的“l”函数,表明后面的参数列表是要传递给程序的参数列表,参数列表的第一个参数必须是执行程序,最后一个参数必须是NULL
    • 带有字母“p”的函数,第一参数可以是相对路径或程序名,如果无法立即找到要执行的程序,那么就在环境变量PATH指定的路径中搜索。其他函数的第一个参数则必须是绝对路径名。
    • 带有字母“v”的函数,表明程序的参数列表通过一个字符串数组来传递。这个数组和最后传递给程序的main函数的字符串数组argv完全一样。第一个参数必须是程序名。最后一个参数也必须是NULL
    • 带有字母“e”的函数,用户可以自己设置程序接收一个设置环境变量的数组
  • 示例
int main(void)
{
        char command1[] = "cat",command2[] = "/bin/cat";
        char *parameters[20] = {command1,"/etc/passwd",NULL};
        int pid = fork();
        if(pid == 0)
        {       
                // 注意传递参数时,命令也要传递进去
                if(execl(command2,parameters[0],parameters[1],parameters[2]))
                // if(execvp(command1,parameters))
                {
                        perror("error\n");
                        return 1;
                }
                else
                {       
                        // 下面这个不会输出,因为子进程成功不会返回
                        printf("success\n");
                        return 0;
                }
        }
        wait(NULL);
        return 0;
}

system函数

int systemp(const char* command);

  • 返回:成功返回命令执行的状态,出错返回-1
  • 功能:简化exec函数的使用
  • 头文件
    #include <stdlib.h>
  • system函数内部构建一个子进程,由子进程调用exec函数
  • 等同于/bin/bash -c "command"或者exec("bash","-c","cmd")
  • 示例
int main(void)
{
        char* command = "date";
        // system(command);
        my_system(command);
        return 0;
}
void my_system(char* command)
{
        int pid = fork();
        if(pid == 0)
        {
                execl("/bin/bash","bash","-c",command,NULL);
                perror("error:");
        }
        wait(NULL);
}

进程状态的切换

new ---fork---> runnable <-os scheduler/timeout-> running -return/exit/_exit-> dead
         ↑                        |
         |                   read/write/sleep/pause
         |                        ↓
         ---------------------block suspend

信号

  • 信号signal机制是linux系统中最古老的进程之间的通信机制,解决进程在正常运行中被中断的问题,导致进程的处理流程会发生变化
  • 信号是软件中断
  • 信号是异步事件

    • 不可预见
    • 信号有自己的名称和编号
    • 信号和异常处理机制
  • 信号发生的来源

    • 硬件来源:比如我们按下了键盘或其他硬件故障,信号是由硬件驱动程序产生
    • 软件来源:最常用发送信号的系统函数是kill(),raise(),alarm()和setitimer()等函数,软件来源还包括一些非法运算登操作,软件设置条件(如:gdb调试),信号是由内核产生。

信号无优先级

1~31:非实时信号,发送的信号可能会丢失,不支持信号排队
31~64:实时信号,支持信号排队发送的多个实时信号都会被接收
可在/usr/include/bits/signum.h

信号的处理

进程可以通过三种方式来响应和处理一个信号

  • 忽略信号

    • SIGKILL和SIGSTOP永远不能忽略
    • 忽略硬件异常
    • 进程启动时SIGUSR1和SIGUSR2两个信号被忽略
  • 执行默认操作

    • 每个信号有默认动作大部分信号默认动作是终止进程
  • 捕获信号

    • 告诉内核出现信号时调用自己的处理函数
    • SIGKILL和SIGSTOP不能被捕获

信号变革

  • 信号出现在最早的unix系统中
  • 早期信号模型是不可靠
  • BSD和System V分别对早期信号进行扩展,但是相互不兼容
  • POSIX统一了上述两种模型,提供了可靠信号模型

SIGNAL函数

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

  • 返回:若成功则返回先前的信号处理函数指针,出错则返回SIG_ERR
  • 功能:向内核登记信号处理函数
  • 参数

    • signo

      • 要登记的信号值,一般使用参数的宏,方便辨识
    • func

      • 信号处理函数指针
      • SIG_IGN:忽略信号
      • SIG_DFL:采用系统默认方式处理信号,执行默认操作
  • 示例
int main(void)
{
        // kill -20 pid即可看到函数被执行了
        // signal(SIGTSTP,my_signal_handle);
        // 忽略
        if(signal(SIGTSTP,SIG_IGN) == SIG_ERR)
        {
                perror("error");
        }
        for(int i = 0;i < 6;i++)
        {
                sleep(6);
                printf("for => i = %d\n",i);
        }
        return 0;
}
void my_signal_handle(int sig)
{
        printf("I am my signal handle\n;my pid is %d\nsignal number is %d\n",getpid(),sig);
}

SIGCHLD信号

  • 子进程状态发生变化(子进程结束)产生该信号,父进程需要使用wait调用来等待子进程结束并回收它
  • 避免僵尸进程
  • 示例
void my_sigchld_handle(int sig)
{
        // 当接受到信号时要调用wait函数,否则子进程会是僵尸进程
        wait(NULL);
        printf("excute wait;pid = %d\n",getpid());
}
int main(void)
{
        // 子进程结束时会执行
        signal(SIGCHLD,my_sigchld_handle);
        int pid = fork();
        if(pid > 0)
        {
                for(int i = 0;i < 66;i++)
                {
                        sleep(1);
                        printf("I am parent;pid = %d;count = %d\n",getpid(),i);
                }

        }
        else if( pid == 0 )
        {
                
                for(int i = 0;i < 6;i++)
                {
                        sleep(1);
                        printf("I am child;pid = %d;count = %d\n",getpid(),i);
                }
        }
        else
        {
                perror("fork error");
        }
        return 0;
}

信号发送

  • 除了内核和超级用户,并不是每个进程都可以向其他的进程发送信号。
  • 一般的进程只能向具有相同uid和gid的进程发送信号,或向相同进程组中的其他进程发送信号
  • 常用的发送信号的函数有kill(向其他进程发送),raise(向自己发送),alarm(定时器),settimer(定时器),abort(异常终止信号)

kill和raise函数

`int kill(pid_t pid,int signo);

  • 返回:成功返回0,出错返回-1
  • 功能:向指定的进程发送某一个信号
    `int raise(int signo);
  • 返回:成功返回0,出错返回-1
  • 功能:向进程本身发送某一个信号
  • 头文件
#include <signal.c>
  • 参数:

    • pid:接受信号进程的pid

      • pid>0:将信号发送给进程id为pid的进程
      • pid==0:将信号发送给与发送进程同一进程组的所有进程
      • pid<0:将信号发送给进程组id为pid的绝对值的所有进程
      • pid==-1:将该信号发送给发送进程有权限像他们发送信号的系统上的所有进程
    • signo:要发送的信号值
  • kill函数将信号发送给函数或进程组

    • 0为空信号,常用来检测特定进程是否存在
  • 示例
int main(void)
{
        signal(SIGUSR1,my_signal_handle);
        for(int i = 0;i < 66;i++)
        {
                // 程序直接结束
                // raise(SIGUSR1);
                kill(getpid(),SIGSTOP);
        }
        return 0;
}
void my_signal_handle(int signo)
{
        printf("I am signal handle\n");
}

定时器

unsigned int alarm(unsigned int seconds);

  • 返回:0或以前设置的定时器时间余留秒数
  • 头文件
#include <unistd.h>
  • alarm函数可设置定时器,当定时器超时,产生SIGALRM
  • 信号由内核产生,在指定的seconds秒之后,给进程本身发送一个SIGALRM信号
  • 参数为0,取消以前设置的定时器。
  • 一次性的
  • ualarm函数精确性更高
  • 示例
int main(void)
{
        signal(SIGALRM,my_sigalarm_handle);
        // 六秒后发送SIGALRM信号到本进程
        alarm(6);
        for(int i = 0;i < 66;i++)
        {
                printf("i = %d\n",i);
                sleep(1);
        }
}
void my_sigalarm_handle(int signo)
{
        printf("sigalrm signo = %d\n",signo);
        // 每隔五秒发送一次信号
        alarm(6);
        // 信号注册函数也需要重新执行下
        signal(SIGALRM,my_sigalarm_handle);
}

linux c内存分配方式以及动态内存分配

从静态存储区分配(由系统控制)

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,staic变量

在栈上分配(由系统控制)

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,凡事分配的内存容量有限

在堆上分配,亦称动态内存分配(由自己控制)

程序在运行时用malloc等函数申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生命周期有我们自己决定,使用非常灵活,但问题也最多

malloc函数

void *malloc(size_t size);

  • 使用:需要包含malloc.h头文件
  • 功能:允许从空闲内存池中分配连续内存但不初始化
  • 参数:size参数是一个所需字节
  • 返回:若分配成功则返回一个指向该内存块的指针,在使用时可根据需要做强制类型转换,否则返回NULL指针
calloc函数

void *calloc(size_t num_elements,size_t element_size);

  • 使用:需要包含malloc.h头文件
  • 功能:功能与malloc函数相同,但作初始化
  • 参数:num_elements是所需元素的数量,element_size是每个元素的字节
  • 返回:与malloc相同
realloc含糊

void *realloc(void *ptr,size_t new_size)

  • 使用:需要包含malloc.h头文件
  • 功能:在指针ptr指向的内存基础上扩大或缩小内存
  • 参数:ptr是指向先前通过malloccallocrealloc函数后分配的内存块的指针,new_size是内存块的新尺寸,可能大于或小于原有内存尺寸
  • 返回:若能调整内存大小则返回指向调整后内存的指针,否则返回NULL
realloc函数的使用注意点
  • 当扩展内存时,不会对添加的内存块的字节进行初始化
  • 弱不能调整内存则返回NULL,但原有内存中的数据不会发生改变
  • 若第一个参数为NULL那么功能等同于malloc函数,若第二个参数为0,那么会释放调用内存块。
  • 在缩小或扩大内存时一般不会对其进行移动,若无法扩大内存块(内存块后面的字节已经用于其他木的),那么可能会在别处分配心的内存块,然后把旧块的数据复制到新块中,并将旧块删除释放内存
free函数

void free(void *ptr);

  • 功能:释放由指针ptr指向的动态内存分配的内存块
free函数注意点
  • 如果ptrNULL指针,那么freeptr无论操作多少次都不会出问题
  • 如果ptr不是NULL指针,那么freeptr连续操作两次就会导致程序停止运行

示例

// stdlib会包含malloc
#include <stdlib.h>
#include <stdio.h>
void print_arr(int *p,int length);
int main(void)
{
        size_t length = 0;
        scanf("%d",&length);
        if(length > 0)
        {
                // 使用malloc分配内存
                //int *p = (int*)malloc(length);
                // 使用calloc分配内存
                int *p = (int*)calloc(sizeof(int),length);
                p[0] = 8;
                print_arr(p,length);
                // 使用 realloc修改申请的内存空间的大小
                p = realloc(p,length + 2);
                print_arr(p,length + 2);
                free(p);
                // 注意虽然释放了申请的内存,但是这个注意要清空下指针
                p = NULL;
        }
        return 0;
}
void print_arr(int *p,int length)
{
        for(int i = 0;i < length;i++)
        {
                printf("p[%d] = %d;\n",i,p[i]);
        }
}

gdb core 使用流程

打开core dump开关

查看core文件大小,系统默认为0

ulimit -c

设置core文件的大小,单位为kbyte

ulimit -c size

设置core文件存储位置
查看core文件存储位置,默认回存存储到当前目录的core.进程id

cat /proc/sys/kernel/core_pattern

设置core文件位置

echo "/tmp/core.%e.%p.%t" > /proc/sys/kernel/core_pattern

编译程序加上-g选项

gcc -g -o 可执行文件 源文件

运行可执行文件

运行过程中如果有问题会产生core文件

定位段错误位置

使用gdb载入调试程序和问题发生时产生的core文件

gdb 可执行文件 core文件

gdb高级命令

工作环境相关命令

命令格式含义
set args 运行时的参数指定运行时的参数,如 set args 2
show args查看设置好的运行参数
path dir设定程序的运行路径
show paths查看程序的运行路径
set environment var[=value]设置环境变量
set var=value修改运行时变量的值
show environment [var]查看环境变量
cd dir进入到dir目录,相当于shell中的cd命令
pwd显示当前工作目录
shell command运行shell的command命令

设置断点和恢复命令

命令格式含义
info b查看所设置的断点
break(b) [行号|函数名] <条件表达式>设置断点
tbreak(b) [行号|函数名] <条件表达式>设置临时断点,到达后自动删除
delete [断点号]删除指定断点
disable [断点号]停止指定的断点名,使用info b仍然能查看此断点。同delete一样,省略断点号则停止所有断点
enable [断点号]激活指定断点,省略则激活所有断点号
condition [断点号] <条件表达式>修改对应断点的条件
ignore [断点号] <num>在程序执行时,忽略对应断点n次
step(s)单步恢复程序运行,且进入函数调用
next(n)单步恢复程序运行,但不会进入函数调用
until(u) 行号跳到指定行
finish运行程序,直到当前函数完成返回
continue(c)继续执行函数,直到函数结束或遇到新的断点

查看源码相关命令

命令格式含义
list(l) <行号>|><函数名>查看指定位置代码
file [文件名]加载指定文件
forward-search 正则表达式源代码前后搜索
reverse-search 正则表达式源代码向前搜索
show directories显示定义了的源文件搜索路径
info file显示加载到gdb内存中的代码
disassemble 函数名查看指定函数的反汇编代码

查看运行数据相关命令

命令格式含义
print(p) 表达式|变量查看程序运行时对应表达式和变量的值
x /<n/f/u> <addr>查看内存变量内容。其中n为整数表示显示内存的长度,f表示显示的格式(如:d表示十进制,x表示16进制,o表示八进制,t表示二进制),u表示从当前的地址往后请求显示的字节数,<addr>表示变量的内存的地址
display 表达式设定在单步运行或其他情况中,自动显示的对应表达式的内容(如:display /i $pc 显示c代码和汇编代码)

操作实例

添加删除断点
(gdb) b 5 # 在第五行加个断点
Breakpoint 1 at 0x400483: file src/gdb_test.c, line 5.
(gdb) info b # 查看所有的断点的信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400483 in main at src/gdb_test.c:5
(gdb) delete 1 # 根据断点的编号删除断点
(gdb) info b # 再次查看,可以发现断点已经没了
No breakpoints or watchpoints.
打印变量
(gdb) p a
$1 = 1
(gdb) p &a
$2 = (int *) 0x7fffffffe454
(gdb) x /d 0x7fffffffe454 # 十进制方式输出
0x7fffffffe454:    1
(gdb) x /x 0x7fffffffe454 # 十六进制输出
0x7fffffffe454:    0x00000001
(gdb) x /o 0x7fffffffe454 # 八进制输出
0x7fffffffe454:    01
(gdb) x /t 0x7fffffffe454 # 二进制输出
0x7fffffffe454:    00000000000000000000000000000001

curl检测Web 站点的响应时间

命令:

curl -o /dev/null -s -w %{time_connect}:%{time_starttransfer}:%{time_total} http://jinblog.com
2.554:2.984:3.040

输出通常是 HTML 代码。

参数:

-o 参数发送到 /dev/null。
-s 参数去掉所有状态信息。
-w 参数让 curl 写出表 1 列出的计时器的状态信息:
计时器 描述
time_connect 建立到服务器的 TCP 连接所用的时间
time_starttransfer 在发出请求之后,Web 服务器返回数据的第一个字节所用的时间
time_total 完成请求所用的时间

以上计时器都相对于事务的起始时间,甚至要先于 Domain Name Service(DNS)查询。

因此,在发出请求之后,Web 服务器处理请求并开始发回数据所用的时间是 0.272 - 0.081 = 0.191 秒。

客户机从服务器下载数据所用的时间是 0.779 - 0.272 = 0.507 秒。

通过观察 curl 数据及其随时间变化的趋势,可以很好地了解站点对用户的响应性。

转载自 http://www.jbxue.com/LINUXjishu/13381.html