Linux进程与线程

进程与线程

进程

进程基本概念

  • 进程:操作系统对程序的加载并运行的动态概念

  • 进程在内核中的组织形式:进程控制块PCB

  • 进程控制块的组织结构

    • 物理组织结构:进程pcb调度队列
    • 逻辑组织结构:进程创建过程中形成的前后逻辑树关系
  • 进程状态以及状态切换

    • TASK_RUNNING
    • TASK_INTERRUPTIBLE
    • TASK_UNINTERTUPTIBLE
    • TASK_SOTPPED
    • TASK_ZOMBILE
  • 进程的文件管理:task_struct索引机制

  • 进程的内存管理:task_struct索引机制

  • 进程的主要特点:

    • 调度和执行:进程状态转换
    • 资源
      • 内核空间:PCB进程控制块
      • 用户空间:正文段、数据段、堆栈段和打开的文件等

进程基本属性

  • ps -aux: process status 查看系统正在运行的所有进程的详细属性

  • 进程ID:获取进程IDgetpid(void)

  • 进程真实用户和真实用户组:获取进程真实用户ID getuid(void),获取进程真实用户组IDgetgid(void)

  • 进程有效用户和有效用户组:获取进程有效用户ID geteuid(void),获取进程有效用户组IDgetegid(void)

  • 有效用户与真实用户实例

    #include <stdio.h>
    #include <stdlib.h>
    #incldue <unistd.h>
    #include <sys/types.h>
    int main(int argc, char const *argv[])
    {
        printf("real uid: %d,real gid: %d \n",getuid(),getgid());
        printf("effective uid: %d, effective gid: %d\n",geteuid(),getegid());
        exit(0);
    }
    /*
    ./3.1.1         //查看真实用户和有效用户的ID
    ls -l 3.1.1     //查看是否设置了用户ID
    chmod u+s 3.1.1 //添加文件特殊属性用户ID位
    su              //切换到root权限
    chown root 3.1.1//改变文件的所有者
    ./3.1.1         //查看有效用户ID是否发生变化 有效用户ID会变成其所有者的用户ID
    */
    
  • 用户密码

    • /etc/password:存储所有用户信息
    • etc/shadow:存储用户密码
    • password:修改用户密码命令,其有效用户ID为root
    • LINUX根据进程的的有效用户进行权限检查

进程生命周期

  • 进程的启动:进程代码的入口main(int argc,char const *argv[]),内核启动C程序是,在调用main函数之前调用特殊的启动函数获取main函数地址和传递给它的参数,并将这些信息写入pcb

  • 进程的终止

    • 正常终止,从main函数返回;调用exit()或者_exit()终止;最后一个线程从其启动例程中返回;最后一个线程调用pthread_exit()终止
    • 异常终止,调用abort()终止
  • 进程的生命周期

    • 调用:C启动例程 >> main() >> 用户函数 返回:用户函数 >> main() >> C例程
    • 调用exit() >> 终止处理函数 >> 标准I/O清理函数 >> 调用_exit()返回到kernel
  • 终止进程的函数

    • void exit(int status):执行完终止处理函数和标准I/O处理函数之后才会返回内核

    • void _exit(int status):终止当前进程的生命周期,并立即返回内核

    • exitreturn的区别:

      1. return是关键字,exit()是API
      2. 在main函数中两者产生相同的效果
      3. return退出的是函数,从子函数中返回;而exit()则会终止进程
    • 终止处理函数:日志登记、资源释放等善后工作,通过atexit()/on_exit()注册若干个终止处理函数

创建进程

  • 进程0:内核创建的进程,进程0创建init进程(进程1)后转为交换进程或者空闲进程

  • 进程1:init进程是系统中所有其他进程的祖先pstree

  • 创建子进程fork():在子进程中返回值为0提示正在子进程中,在父进程中返回值为子进程pid

    • 子进程拷贝父进程的PCB和数据空间(数据段和堆栈)
    • 子进程和父进程共享正文段
    • 子进程和父进程都会执行调用fork()之后的代码
    int main(int argc, char const *argv[])
    {
        pid_t pid;
        pid = fork();
        if(pid == -1)
            printf("fork error\n");
        else if(pid == 0){
        	printf("the returned value is %d \n",pid);
            printf("in child process\n");
            printf("My pid is %d\n",getpid());
        }else{
            printf("the returned value is %d \n",pid);
            printf("in father process\n");
            printf("My pid is %d\n",getpid());
        }
        return 0;
    }
    
  • 创建进程的操作:

    • 为进程分配内核空间即进程控制块PCB
    • 为进程分配用户空间报告正文段、数据段、堆(动态分配的空间)、栈(函数调用)
  • 父子进程的异同

    相同

    • 真实和有效用户ID相同
    • 环境变量相同
    • 数据空间相同
    • 打开的文件相同(父子进程文件共享)

    不同

    • fork()函数的返回值不同
    • 进程ID不同
    • 子进程的时间时钟数据会被清零
  • 创建独立进程vfork()fork创建新进程将拥有自己的地址空间,但是在调用execexit离开父进程之前是在父进程的地址空间中运行,并在此期间子进程优先执行而父进程处于阻塞状态

  • exec系列函数:进程调用exec系列函数后,进程会变成执行该函数的进程,exec程序将改变进程的用户空间内容包括正文段、数据段和堆栈,并从加载的可执行文件的main函数开始重新执行。调用exec系列函数不改变进程的PCB所以不产生新的进程。

    • exec系列函数作用是提供可执行文件的命令行参数或者环境变量

    • execl : int execl(const char *pathname, const char *argv0,...,NULL);

    • int main(int argc,char const *argv[])
      {
       	printf("entering main process \n");
          if(fork()==0)
          {
              execl("/bin/ls","ls","-l",NULL);//参数个数根据NULL结尾获得
              printf("exiting main process\n");//execl可执行文件的代码会覆盖父进程的正文段,execl以后的代码都不会执行且不会有返回值
          }
          return 0;
      }
      
    • execle : int execle(const char *pathname, const char *argv0,...,NULL,char *envp[]);

    • #include <unistd.h>
      #include <stdio.h>
      #include <stdlib.h>
      
      int main()
      {
          char *envp[] = {"PATH=/tmp","USER=shan",NULL};//环境变量字符串
          if(fork()==0)
          {
              if(execle("/bin/ls","ls","-l",NULL,evnp)<0)
                  perror("execle error\n");
          }
          return 0;
      }
      
    • execlp : int execle(const char *pathname, const char *argv0,...,NULL,char *envp[]);

    • execv : int execv(const char *pathname,char *const argv[]);

    • int main(int argc,char const *argv[])
      {
          int ret;
          char *argv[] = {"ls","-l",NULL};
       	printf("entering main process \n");
          if(fork()==0)
          {
              ret = execv("/bin/ls",argv);
              if(ret == -1)
                  perror("execv error");
              printf("exiting main process\n");
          }
          return 0;
      }
      
    • execve : int execve(const char *pathname, char *const argv[],char *envp[]);

    • execvp: int execvp(const char *pathname,char *const argv[]);

    • l : list 提供一系列命令行参数

    • v:vector 命令行参数放在一个数组中

    • e:由函数调用者提供环境变量表

    • p:path 指定环境变量路径,查找可执行文件

线程

线程的基本概念

  • 线程:进程内的独立执行代码的实体和调度单元

  • 一个进程内的多个线程的关系:

    • 共享:共享打开的文件、全局变量等资源
    • 私有:线程ID、存放局部变量的栈、寄存器集合(PC指针和上下文环境)等
  • 没有通过代码显示创建线程的进程看作只有一个线程的进程

  • 线程ID:pthread_t仅仅在进程环境中是唯一的

线程和进程的操作

操作进程操作API线程操作API
线程创建fork(),vfork()pthread_create()
终止exit()pthread_exit(),pthread_cancel()
等待wait(),waitpid()pthread_join()
获取IDgetpid()pthread_self()
  • 创建子线程实例

    void *childthread(void)
    {
        int i;
        for(i=0;i<10;i++)
        {
            printf("childthread message\n");
            sleep(100);
        }
    }
    
    int main(int argc,char const *argv[])
    {
        pthread_t tid;
        printf("create childthread\n");
        pthread_create(&tid,NULL,(void*)childthread,NULL);
        pthread_join(tid,NULL);
        printf("childthread exit\n");
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王清欢Randy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值