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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现