LINUX第三章学习笔记——Unix/Linux 进程管理

第三章 Unix/Linux 进程管理


多任务处理

  • 指的是同时进行几项独立活动的能力

逻辑并行性称为“并发”

  • 多个CPU或处理器内核的多处理器系统中,可以在不同CPU上实时并发执行多项任务

什么是进程?

进程是对映像的执行

如下是一个非常简单的PROC结构体:

  • next是指向下一个PROC结构体的指针
  • ksp保存的堆栈指针
  • pid是一个进程的进程编号
  • status是当前状态
  • priority是进程调度优先级
  • kstack是进程执行时的堆栈

多任务处理系统

1.type.h文件

文件定义了系统常熟和表示进程的简单PROC结构体

#define FREE 0
#define READY 1
#define SLEEP 2
#define ZOMBIE 3
typedef struct proc
{ 
   struct proc *next;
   int *ksp;
   int pid;
   int ppid;
   int status;
   int priority;
   int kstack[SSIZE];
}

2.ts.s文件

在32位GCC汇编代码中可实现进程上下文切换

3.queue.c文件

可实现队列和链表操作

int enqueue(PROC **queue,PROC *p)
{ 
   PROC *p = *queue;
   if(q==0||p->priority > q)
}

4.t.c文件

多任务处理系统代码

gcc -m32 t.c ts.s
运行整个a.out,整个MT系统在用户模式下作为LINUX进程运行

  • init() :当MT系统启动时,main()函数调用init()以初始化系统。
  • P0调用kfork()来创建优先级为1的子进程P1,并将其输入就绪队列中。
  • tswitch():tswitch()函数实现进程上下文切换。
  • kfork():创建一个子任务并将其输入readyQueue中。
  • body():为便于演示,所有创建的任务都执行同一个body()函数。
  • 空闲任务P0:在所有任务中具有最低的优先级。

进程同步

睡眠模式

唤醒模式

kwakeup()算法:

进程终止

正常终止kexit()和异常终止

kexit()算法

进程家族树

PROC *child,*sibling,*parent;

等待子程序终止

pid = kwait(int *status)
可等待僵尸子进程,成功后返回僵尸子进程的pid
kwait()算法如下:

Unix/Linux中的进程

  • 进程来源
    当操作系统启动时,操作系统内核的启动代码会强行创建一个PID=0初始进程。
    执行初始进程P0
  • INIT和守护进程
    当进程P1开始运行时,它将其执行映像更改为INIT程序。因此,P1通常被称为INIT进程,因为它的执行映像是init程序。P1 开始复刻出许多子进程。
    P1的大部分子进程都是用来提供系统服务的。它们在后台运行,不与任何用户交互。
  • 登录进程
    P1复刻了许多LOGIN进程,每个终端上一个,用于用户登录。
  • sh进程
    当用户成功登录时,LOGIN进程会获取用户的gid和uid,从而称为用户的进程。他将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序sh。

进程的执行模式

在Unix/Linux中进程以两种不同的模式执行,即内核模式和用户模式,简称Kmode和Umode。在每种执行模式下,一个进程有一个执行映像。

进程管理的系统调用

fork()函数

示例:

#include<stdio.h>
int main()
{
    int pid;
    printf("THIS IS %d MY PARENT=%d\n",getpid(),getppid());
    pid=fork();
    if(pid){
        printf("THIS IS PROCESS %d CHIL PID=%d\n",getpid(),pid);
        
    }
    else{
        printf("this is process %d parent=%d\n",getpid(),getppid());
    }
}


进程执行顺序

示例:

#include<stdio.h>
int main()
{
    int pid = fork();
    if(pid){
        printf("PARENT %d CHILD = %d\n",getpid(),pid);
        sleep(1);
        printf("PARENT %d EXIT\n",getpid());
    }
    else{
        printf("child %d start my parent=%d\n",getpid(),getppid());
        sleep(2);
        printf("child %d exit my parent=%d\n",getpid(),getppid());
    }
}


等待子进程终止

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int pid,status;
    pid=fork();
    if(pid){
        printf("PARENTA %d WAITS FOR CHILD %d TO DIE\n",getpid(),pid);
            pid = wait(&status);
            printf("DEAD CHILD=%d,status=0x%04x\n",pid,status);
    }
        else{
            printf("child %d dies by exit(VALUE)\n",getpid());
            exit(100);
        }
}

LINUX中的subreaper进程

进程可以用系统调用将自己定位为subreaper
proct1(PR_SET_CHILD_SUBREAPER)
同样每个用户init进程均标记为subreaper,可以使用sh命令
ps fxau | grep USERNAME | grep "/sbin/upstart"
示例进程:

#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<sys/prct1.h>
int main()
{
    int pid,r,status;
    printf("mark process %d as a subreaoer\n",getpid());
    r=prct1(PR_SET_CHILD_SUBREAPER);
    pid=fork();
    if(pid){
        printf("subreaper %d child = %d\n",getpid(),pid);
        while(1){
            pid=wait(&status);
            if(pid>0)
            printf("subreaper %d waited a ZOMBIE=%d\n",getpid(),pid);
            else
            break;
        }
    }
    else{
        printf("child %d parent=%d\n",getpid(),(pid_t)getppid());
        pid=fork();
        if(pid){
            printf("child=%d start:grandchild=%d\n",getpid(),pid);
            printf("child=%d EXIT:grandchild=%d\n",getpid(),pid);
        }
        else{
            printf("grandchild=%d start:myparent=%d\n",getpid(),getppid());
            printf("grandchild=%d EXIT : myparent=%d\n",getpid(),getppid());
        }
    }
}

exec():更改进程执行映像

环境变量

可以使用env和printenv命令查看环境变量

示例:如何通过execve()更改进程映像

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *dir[64],*myargv[64];
char cmd[128];
int main(int argv,char *argv[],char *env[])
{
    int i,r;
    printf("THIS IS PROCESS %d IN %s\n",getpid(),argv[0]);
    if(argc<2){
        printf("Usage:a.out command [options]\n");
        exit(0);
    }
    printf("argc=%d\n",argc);
    for(i=0;i<argc;i++)
    printf("argv[%d]=%s\n",i,argv[i]);
    for(i=0;i<argc-1;i++)
    {
        myargv[i]=argv[i+1];
    }
    myargv[i]=0;
    strcpy(cmd,"/bin/");
    strcat(cmd,myargv[0]);
    printf("cmd=%s\n",cmd);
    int r=execve(cmd,myargv,env);
    printf("execve() failed: r=%d\n",r);
}

I/O重定向

文件流和文件描述符

文件流I/O和系统调用

进程执行库函数:
scanf("%s",&item)

重定向标准输入

close(0)
关闭文件描述符0,使0成为未使用的文件描述符

int fd=open("filename",O_RDOMLY);
close(0);
dup(fd);

系统调用dup(fd)将fd复制到数值最小的未使用文件描述符中,允许fd,0都能访问同一个打开的文件,同时可以系统调用:
dup2(fd1,fd2)
将fd1复制到fd2,如果fd2已经打开,则先关闭它

管道编程

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int pd[2],n,i;
char line[256];
int main()
{
    pipe(pd);
    printf("pd=[%d,%d]\n",pd[0],pd[1]);
    if(fork()){
        printf("parent %d close pd[0]\n",getpid());
        close(pd[0]);
        while(i++ < 10){
            printf("parent %d writing to pipe\n",getpid());
            n=write(pd[1],"I AM YOUER PAPA",16);
            printf("parent %d wrote %d bytes to pipe\n",getppid(),n);
        }
        printf("parent %d exit\n",getpid());
    }
    else{
        printf("child %d close pd[1]\n",getpid());
        close(pd[1]);
        while(1){
            printf("child %d reading from pipe\n",getpid());
            if((n=read(pd[0],line,128))){
                line[n]=0;
                printf("child read %d bytes from pipe:%s\n",n,line);
            }
            else
            exit(0);
        }
    }
}

命名管道

问题和解决方法:

1.#include<sys/prct1.h>头文件出现问题
解答:应用程序的头文件在/usr/include下(比如fedora9下),因为应用程序是在fedora9下编译的,所以应用程序的头文件一定要来自fedora9下,而把编译的可行性文件如test,通过nfs挂载到开发板上运行的时候,内核中的头文件都是在如linux2.6.24,它里面的都是内核用的头文件。
位于linux系统下/usr/include/sys/文件下面
POSIX标准定义的头文件
<dirent.h> 目录项
<fcntl.h> 文件控制
<fnmatch.h> 文件名匹配类型
<glob.h> 路径名模式匹配类型
<grp.h> 组文件
<netdb.h> 网络数据库操作
<pwd.h> 口令文件
<regex.h> 正则表达式
<tar.h> TAR归档值
<termios.h> 终端I/O
<unistd.h> 符号常量
<utime.h> 文件时间
<wordexp.h> 字符扩展类型
2.命名管道具体含义?
命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
命名管道(FIFO)和无名管道(pipe)有一些特点是相同的,不一样的地方在于:
1、FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。
2、当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
3、FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。
https://blog.csdn.net/lianghe_work/article/details/47722175)

posted @ 2022-10-09 17:16  20201303张奕博  阅读(99)  评论(0编辑  收藏  举报