Linux系统编程1:进程基础

Linux系统编程1:进程基础_第1张图片

从一次元世界进入二次元世界,不只是思维的改变,而是世界观的改变。

程序与进程

如果程序是菜谱,进程就是厨师烹饪;
如果程序是乐谱,进程就是乐师演奏;
如果程序是剑谱,进程就是剑客舞剑;
如果程序是棋谱,进程就是棋士复盘;
程序是静的,进程是动的。

进程与程序区别

No. 进程 程序
1 动态 静态
2 有生命周期 指令集合
3 只能对应一个程序 可以对应多个进程

概念

进程:程序在计算机上的一次执行过程,执行中的程序。

  • 进程是一个抽象概念
No. 组成 含义 类比
1 一个独立的逻辑控制流 独占处理器 工人/机器
2 一个私有的地址空间 独占存储器系统 工厂
  • 本质
    • 程序在地址空间中按照代码逻辑控制流执行
    • 资源分配最小单位

从代码到程序

Linux系统编程1:进程基础_第2张图片

从程序到进程

  1. 内核将程序读入内存,为程序镜像分配内存空间。
  2. 内核为该进程分配进程标志符PID。
  3. 内核为该进程保存PID及相应的进程状态信息。
Linux系统编程1:进程基础_第3张图片

进程控制块(PCB):保存进程控制信息

程序格式ELF

ELF(Executable and Linkable Format)文件格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。

查看程序(ELF文件):readelf -S 文件名
查看进程空间大小:size 文件名

虚拟存储器/虚拟地址空间

Linux系统编程1:进程基础_第4张图片
虚拟存储器
段名 组成 来源
代码段 .text 可执行文件
数据段 .data bss 可执行文件
堆栈段 heap stack 请求
变量 位置
经过初始化的全局变量和静态变量 .data
未经初始化的全局变量和静态变量 .bss
函数内部声明的局部变量 stack
const修饰的全局变量 .text
const修饰的局部变量 stack
字符串常量 .text

.bss(Block Started by Symbol)存放程序中未初始化的全局变量和静态变量,程序执行之前BSS段会自动清0。

详细资料

  • ELF文件格式与相关命令
  • ELF文件格式浅析
  • ELF文件格式分析

进程状态

Linux系统编程1:进程基础_第5张图片

类似视频/音频播放器

No. 状态 含义
1 就绪(Ready) 进程已获得到除CPU以外的所有必要的资源,获得CPU立即执行
2 运行(Running) 程序正在CPU上执行
3 阻塞(Blocked) 等待某个事件发生而无法执行时,放弃CPU

如何查看进程

No. OS 命令 e.g.
1 Windows tasklist tasklist /FI "PID eq 进程PID"
2 Linux ps / pstree / top -

ps命令

  • 查看某进程
    通过进程PID查看:ps -p 进程PID
    通过命令行查看:ps -C 命令行

  • 查看进程

No. 风格 命令 属性说明
1 BSD风格 ps aux a: 终端上所有用户的进程;u:以用户为中心显示详细信息,x:无终端进程
2 System V风格 ps -ef e:所有进程;f:树状显示

Unix从操作风格分为System V风格和BSD风格。它们在目录结构、脚本、命令行等方面存在一些差异。

  • System V, 曾经也被称为AT&T SystemV,是Unix操作系统众多版本中的一支。它最初由AT&T开发。
  • BSD(BerkeleySoftware Distribution,伯克利软件套件)是Unix的衍生系统,1970年代由伯克利加州大学(UniversityofCalifornia, Berkeley)开发。
    随着一些并不基于这两者代码的UNIX实现的出现,例如Linux,这一归纳不再准确。像POSIX这样的标准化努力一直在试图减少各种实现之间的不同。
  • 列表示说明
No. 标识 含义
1 USER 用户
2 PID 进程ID
3 %CPU 进程占用的CPU百分比
4 %MEM 占用内存的百分比
5 VSZ 进程虚拟大小
6 RSS 常驻内存(内存中页的数量)
7 TTY 终端ID
8 STAT 进程状态
9 START 启动进程的时间
10 TIME 进程消耗CPU的时间
11 COMMAND 命令的名称和参数
  • 进程状态标识
No. 标识 含义
1 D 不可中断Uninterruptible(usually IO)
2 R 正在运行,或在队列中的进程
3 S 处于休眠状态
4 T 停止或被追踪
5 Z 僵尸进程
6 W 进入内存交换(从内核2.6开始无效)
7 X 死掉的进程
8 < 高优先级
9 n 低优先级
10 s 包含子进程
11 + 位于后台的进程组

pstree命令

以树状图的方式展现进程之间的派生关系

  • 安装
yum install psmisc

top命令

实时显示系统中各个进程的资源占用,类似Windows任务管理器。

Linux一切皆文件,在/proc/下也可以查看到进程。

如何创建进程

No. OS 命令
1 Windows 程序名
2 Linux 程序名

如何杀死进程

No. OS 命令
1 Windows taskkill /F /PID 进程标识/taskkill /F /IM 程序名
2 Linux kill 进程标识PID

进程操作接口

获取PID

进程标识pid:进程身份证号

No. 函数 接口
1 pid_t getpid() 获取当前进程ID
2 pid_t getppid() 获取当前进程父进程ID
  • 示例
#include 
#include 
 
int main(){
    printf("PID:%dPPID:%d\n",getpid(),getppid());
}

查看进程的PID和PPID

ps -o pid,ppid,cmd,s

如何创建进程

1 分叉函数pid_t fork()

  • 返回值
No. 返回值 含义
1 -1 失败
2 0 子进程逻辑控制流
3 其他(子进程PID) 父进程逻辑控制流
  • 特点

    • 调用一次,返回两次
    • 相同但是独立的地址空间
    • 并发执行
    • 共享文件
  • 示例

调用一次,返回两次

#include 
#include 
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    fork();
    if(pid == 0){// child
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }
    for(;;);
}

相同但是独立的地址空间&并发执行

#include 
#include 
int i = 100;
int main(){
    int j=100;
    pid_t pid = fork();
    if(pid == 0){// child
        int k;
        for(k=0;k<10000;k++)
            printf("this is childi%d\t j%d\n",++i,++j);
    }else{
        int k;
        for(k=0;k<10000;k++)
            printf("this is fatheri%d\t j%d\n",--i,--j);
    }
}

共享文件

#include 
#include 
int i = 100;
int main(){
    int j=100;
    FILE* fd = fopen("./test","w+");
    pid_t pid = fork();
    if(pid == 0){// child
        int k;
        for(k=0;k<10000;k++)
            fprintf(fd,"this is childi%d\t j%d\n",++i,++j);
    }else{
        int k;
        for(k=0;k<10000;k++)
            fprintf(fd,"this is fatheri%d\t j%d\n",--i,--j);
    }
}
  • 本质
    复制+分叉
No. 概念 状态 硬件 特点
1 并发(concurrency) 两个或者多个进程在同时存在 单核 进程指令同时或者交错执行。
2 并行(parallellism) 两个或者多个进程在同时执行 多核 一种特殊的并发

2 执行函数exec()

  • 分类
No. 分类 函数
1 字符串数组参数 execv()execvp()execve()
2 可变参数 execle()execlp()execl()
  • exec函数组名字规律
No. 字符 含义
1 v 第二个参数是数组
2 l 第二个参数之后是变参
3 p 第一个参数是文件名
4 e 最后一个参数是环境变量
  • 返回值
No. 返回值 含义
1 -1 失败
2 不返回 成功
  • 特点
    • 一次调用,失败返回
    • 改朝换代,取而代之
      • PID不变
      • 地址空间内容变化
  • 示例
#include 
#include 
#include 
 
extern char ** environ;
 
int main(int argc,char** argv){
    printf("%s,PID %d\n",argv[0],getpid());
    //execl("/bin/ps","ps","-a","-o","pid,ppid,cmd",0);
    //execlp("ps","ps","-a","-o","pid,ppid,cmd",0);
    //execle("/bin/ps","ps","-a","-o","pid,ppid,cmd",0,environ);
    char* args[] = {"ps","-a","-o","pid,ppid,cmd",0};
    //execv("/bin/ps",args);
    execve("/bin/ps",args,environ);
    //execvp("ps",args);
    printf("%s,PID %d\n",argv[0],getpid());
}
  • 本质
    覆盖程序

3 系统函数int system(Shell字符串)

  • 返回值
No. 返回值 含义
1 -1 失败
2 127 无法启动shell来运行
3 其他 命令退出码
  • 特点
    一次调用,一次返回
  • 示例
#include 
#include 
#include 
 
int main(int argc,char** argv){
    printf("PID:%d\n",getpid());    
    system("sleep 3&");
    printf("PID:%d\n",getpid());    
}
  • 本质
    shell执行命令/程序

Linux系统可以创建多少个进程?使用ulimit -a可以查看到。可以通过ulimit -u 进程数修改。
更详细的设置可以在/etc/security/limits.conf修改。

如何结束进程

No. 方式 说明
1 main函数退出 只能用在main函数内
2 调用exit()函数 一般用在main函数以外的函数
3 调用_exit()函数 一般用来结束子进程
4 调用abort()函数 一般用来异常退出
5 信号终止 终止其他进程
  • 示例

return退出

#include 
#include 
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());     
    return 100;
}

exit()/abort()退出

#include 
#include 
#include 
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    //exit(EXIT_FAILURE);
    abort();
}

如何停止进程

休眠

int sleep(unsigned int secs)

  • 参数
    secs指定休眠的秒数,-1表示永久休眠
  • 返回值
    未休眠的秒数
  • 特性
    如果没有信号中断,休眠指定秒数返回0,否则马上返回未休眠的秒数。
  • 示例:电子时钟
#include 
#include 
#include 
#include 
 
int main(){
    int i = 0;
    for(;;){
        time_t t;
        time(&t);
        struct tm* ptm =  gmtime(&t);
        char buf[BUFSIZ];
        bzero(buf,BUFSIZ);
        strftime(buf,BUFSIZ,"%P %T",ptm);
        printf("\r%s",buf);
        fflush(stdout);
        sleep(1);
    }
}

暂停

int pause()

  • 返回值
    总是-1
  • 特性
    • 如果程序没有处理信号,直接中断,执行默认信号处理,程序后续代码不再执行。
    • 如果程序存在信号处理,执行信号处理后,执行后续代码。
  • 等待信号
No. 快捷键 信号 说明
1 Ctrl+C SIGINT 中断
2 Ctrl+Z SIGTSTP 终端的停止信号
  • 示例
#include 
#include 
#include 
void test(int sig){
    printf("revc a signal%d",sig);
}
 
int main(){
    signal(SIGINT,test);
    printf("before pause\n");
    pause();
    printf("after pause\n");
}

等待

  • pid_t wait(int* status):等价pid_t waitpid(-1,stauts,0)

  • pid_t waitpid(pid_t pid,int * status,int options)

  • 参数

No. 参数 含义 说明
1 pid 等待的进程 <-1:等待进程组为pid的所有进程;-1: 等待任何子进程;0:等待同组的进程;>0:进程为pid 的子进程
2 status 子进程结束状态 判断正常结束:使用WIFEXITED(status);判断异常结束使用WIFSIGNALED(status);判断暂停使用WIFSTOPPED(status)
3 options 选项 WNOHANG若子进程没有结束,返回0,不予以等待;若子进程结束,返回该子进程的ID。WUNTRACED若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。
  • 返回值
No. 返回值 含义
1 -1 失败
2 其他 等待的PID
  • 正常结束:WIFEXITED(status)
No. 参数 含义
1 0 正常结束子进程
2 0 非正常结束子进程

WEXITSTATUS(status)取得子进程exit()返回的结束代码
一般会先用WIFEXITED来判断是否正常结束才能使用此宏

  • 异常结束:WIFSIGNALED(status)
No. 参数 含义
1 0 异常结束子进程
2 0 非异常结束子进程

WTERMSIG(status)取得子进程因信号而中止的信号代码
一般会先用 WIFSIGNALED 来判断后才使用此宏

  • 暂停:WIFSTOPPED(status)
No. 参数 含义
1 0 暂停结束子进程
2 0 非暂停结束子进程

WSTOPSIG(status)取得引发子进程暂停的信号代码
一般会先用 WIFSTOPPED 来判断后才使用此宏。

  • 示例
    父进程等待子进程退出
#include 
#include 
#include 
#include 
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        sleep(2);
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        //exit(0);
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        printf("pid:%d exit\n",waitpid(pid,NULL,0));
    }
}

更加安全的方式

#include 
#include 
#include 
#include 
 
void handler(int sig){
    int status;
    pid_t cpid = wait(&status);
    if(WIFEXITED(status)){
        printf("child exit by %d\n",WEXITSTATUS(status));
    }   
    if(WIFSIGNALED(status)){
        printf("child exit by signal %d\n",WTERMSIG(status));
    }
    printf("child %d exit\n",cpid);
}
 
int main(){
    signal(SIGCHLD,handler);
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        sleep(2);
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        //exit(0);
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        printf("leave:%d\n",sleep(5));
        //exit(200);
    }
    for(;;);
}

如果不关心退出的情况。

#include 
#include 
#include 
 
void handler(int sig){
    pid_t cpid = wait(NULL);
    printf("child %d exit",cpid);
}
 
int main(){
    signal(SIGCHLD,handler);
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        sleep(2);
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }
}

进程的终老、他杀与自杀


特殊进程

No. 概念 出现条件 导致结果 是否有害
1 孤儿进程 父进程先于子进程退出 init进程作为新的父进程 无害
2 僵尸进程 子进程退出,父进程没有获取子进程的状态信息 调用waitwaitpid 有害,避免出现僵尸进程
  • 示例
#include 
#include 
#include 
void handle(int sig){
    //wait(NULL);
    printf("this is child exit %d",sig);
}
int main(){
    signal(SIGCHLD,handle);
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        for(;;);
    }
}

你可能感兴趣的:(Linux系统编程1:进程基础)