linux-进程

一、基本概念

  • 程序:存放在磁盘上的指令和数据的有序集合(文件),静态的
  • 进程:执行一个程序所分配的资源的总称,进程是程序的一次执行过程,动态的,包括创建、调度、执行和消亡

1.1、进程内容

在这里插入图片描述
进程包含的内容:

  • BSS段:存放程序中未初始化的全局变量
  • 数据段:已初始化的全局变量
  • 代码段:程序执行代码
  • 堆(heap):malloc等函数分配内存
  • 栈(stack):局部变量,函数参数,函数的返回值
  • 进程控制块(pcb): 进程标识PID ,进程用户 ,进程状态、优先级,文件描述符表

1.2、进程类型

  • 交互进程:在shell下启动。以在前台运行,也可以在后台运行
  • 批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
  • 守护进程:和终端无关,一直在后台运行

1.3、进程状态

  • 运行态:进程正在运行,或者准备运行
  • 等待态:进程在等待一个事件的发生或某种系统资源,可中断,不可中断
  • 停止态:进程被中止,收到信号后可继续运行
  • 死亡态:已终止的进程,但pcb没有被释放
    在这里插入图片描述

二、常用命令

2.1、查看进程信息

ps查看系统进程快照
top查看进程动态信息
/proc查看进程详细信息

ps 命令详细参数:

-e显示所有进程
-l长格式显示更加详细的信息
-f全部列出,通常和其他选项联用
aux相对于ps -ef还能够显示进程的当前状态(运行态、停止态、前台进程等)

例如
top 查看进程动态信息
shift +> 后翻页
shift +< 前翻页
top -p PID 查看某个进程

2.2、前后台进程切换

jobs查看后台进程
bg将挂起的进程在后台运行
fg把后台运行的进程放到前台运行

ctrl+z 把运行的前台进程转为后台并停止。
./test & 把test程序后台运行

2.3、改变进程优先级

nice按用户指定优先级运行进程
renice改变正在运行进程的优先级
nice    按用户指定的优先级运行进程
		nice [-n NI值] 命令
		NI 范围是 -20~19。数值越大优先级越低
		普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
		普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
		只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。
renice  改变正在运行进程的优先级
		renice [优先级] PID
nice -n 2 ./test#将test进程的nice值设置为2
renice -n 2 29070#将进程号为29070的nice值设置为2

三、进程的创建和结束

3.1、创建子进程

#include  <unistd.h>
pid_t  fork(void);

创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程和子进程

#include<stdio.h>
#include<unistd.h>

int main()
{
    pid_t pid;
    if((pid=fork())<0){
	perror("fork");//创建失败,打印错误信息
	return -1;
    }
    else if(pid==0){
	printf("child prosess:my pid is %d\n",getpid());//创建子进程,返回子进程的pid号
    }
    else{
	printf("parent process:my pid is %d\n",getpid());//打印父进程的进程号
    }
}

3.2、父子进程

  • 子进程继承了父进程的内容(几乎赋值了父进程的全部内容,但是pid、ppid不同)
  • 父子进程有独立的地址空间,互不影响
  • 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程
  • 若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程
    注意:
    1. 子进程是从fork函数的下一条语句开始执行,并没有执行fork(子进程只执行fork之后的代码)
    2. 内核先调用父进程就先执行父进程,先调用子进程就先执行子进程(父子进程执行顺序是操作系统决定的)
    3. 父进程可以多次调用fork,子进程也可以调用fork创建孙进程(注意进程的回收)

3.3、结束进程

#include <stdlib.h> 
#include  <unistd.h>
void  exit(int  status);
void  _exit(int  status);

结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	printf("this process will exit");//没有换行符只能写到标准输出流的缓冲区,不会在终端显示
	exit(0);//结束进程,刷新缓冲区的流,因此上条语句会显示在终端上
	printf("never be discovered");//不会被打印	
}

return 和exit的区别
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。

四、进程的回收

#include  <unistd.h>
pid_t wait(int *status);

成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值

#include<stdio.h>
#include<unistd.h>

int main()
{
    int status;//接收返回值以及返回方式
    pid_t pid;//接收fork的返回值
    if((pid=fork())<0){
        perror("fork");
        exit(-1);
    }
    else if(pid==0){
        sleep(1);//如果是子进程睡眠一秒
        exit(2);//退出子进程
    }
    else{
        wait(&status);//等待子进程结束
        printf("%x\n",status);//打印输出结果
    }
}

子进程通过exit/_exit/return返回某个值(0~255)
父进程通过调用wait(&status)回收

WIFEXITED(status)判断子进程是否正常结束
WEXITSTATUS(status)获得子进程的返回值
WIFSIGNALED(status)判断子进程是否被信号结束
WTERMSIG(status)获取结束子进程的信号类型
#include  <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);

wait(wait_stat) 等价于waitpid(-1,wait_stat,0)

  • 成功时返回回收子进程的pid或0;失败(如:没有子线程)返回EOF
  • pid可用于指定回收那个子进程或任意子进程
  • status指定用于保存子进程的返回值和结束方式的地址
  • option指定回收方式,0(阻塞的方式,等待子进程结束)或WNOHANG(非阻塞)

参数:
pid

pid>0只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options

WNOHANG若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED返回终止子进程信息和因信号停止的子进程信息
waitpid(pid,&status,0);//指定进程的进程号,阻塞方式
waitpid(pid,&status,WNOHABG);//子进程结束返回子进程的进程号,没有结束返回0
waitpid(-1,&status,0);//-1表示回收任意一个子进程,等价于wait
waitpid(-1,&status,WNOHANG);//非阻塞的方式回收任意进程

五、exec函数族

背景:fork创建进程之后,子进程和父进程执行相同的代码,但是在实际开发当中,我们希望父子进程执行不同的代码。
作用:执行指定的程序

  • 进程调用exec函数族执行某个程序
  • 进程当前内容被指定程序替换
  • 实现父子进程执行不同程序
  • 父进程创建子进程
  • 父进程调用exec函数族
  • 父进程不受影响
#include  <unistd.h>
int execl(const char *path, const char *arg,);
int execlp(const char *file, const char *arg,);

成功时执行指定的程序;失败时返回EOF
path:执行的程序名称,包含路径
arg :传递给执行的程序的参数列表
file :执行的程序的名称,在PATH中查找

#include <unistd.h>
int execv(const char *path,char *const argv[]);//arg...封装成指针数组的形式
int execvp(const char*file,char *const argv[]);//arg...封装成指针数组的形式 
方式1:execl
if(execl("/bin/ls","ls","-a","-l","/etc",NULL)<0{//一定要以NULL结尾,并判断函数是否执行成功
	perror("execl");
}	

方式2:execlp
if(execlp("ls","ls","-a","-l","/etc",NULL)<0{//会自动在PATH路劲搜索
	perror("execl");
}	
方式3:execv
char *arg[]={"ls","-a","-l","/etc",NULL};//将要传递的参数放在数组中
if(execv("/bin/ls",arg)<0){
	perror("execv");
}
方式3:execvp
if(execvp("ls",arg)<0){
	perror("execvp");//会自动在PATH路径中搜索
}

#include <stdlib.h>
int system(const char *command)

成功时返回command的返回值;失败时返回EOF(自动创建一个子进程,子进程执行命令)
当前进程(父进程)等待command执行结束后才继续执行。

六、守护进程

6.1、概念:

守护进程(Daemon)是Linux三种类型之一,通常在系统启动时运行,系统结束时关闭,Linux系统中大量使用,很多服务程序就是以守护进程形式运行。
Linux以会话(session)、进程组的方式管理进程,每个进程就属于一个进程组。而会话是一个或者多个进程组的集合。通常用户打开一个终端时,系统会创建一个临时会话,所有通过该终端运行的进程都属于这个会话。终端关闭时,所有进程会被结束。那么我们要使得守护进程与终端无关,当终端关闭的时候守护进程依旧可以运行。

进程组(Process Group): 进程集合,每个进程组有一个组长(Leader),其进程 ID 就是该进程组 ID。
会话(Session): 进程组集合,每个会话有一个组长,其进程 ID 就是该会话组 ID。
控制终端(Controlling Terminal):每个会话可以有一个单独的控制终端,与控制终端连接的 Leader 就是控制进程(Controlling Process)。

6.2、特点:

  • 始终在后台运行
  • 独立于任何终端
  • 周期性的执行某种任务或等待处理特定事件

1、创建子进程,父进程退出

if(fork()>0){
	exit(0);
}

子进程变成孤儿进程,被init进程收养
子进程在后台运行,但是依旧和终端相关联

2、子进程创建新会话

if(setsid()<0)//通过setsid创建一个新的会话
{
	exit(-1);
}

子进程成为新的会话组长
子进程脱离原先的终端

3、更改当前工作目录

chdir("/");
chdir("/tmp");//所有用户可读可写可执行

守护进程一直在后台运行,其工作目录不能被卸载
重新设定当前工作目录cwd

4、重设文件权限掩码

if(umask(0)<0){
	exit(-1);
}

5、关闭打开的文件描述符

int i;
for(i=0;i<3;i++)//文件描述符最小就是0。getdtablesize()返回当前进程打开最大文件数
{
	close(i);
}

关闭所有从父进程继承的打开文件
已脱离终端,stdin/stdout/stderr无法再使用

创建守护进程,没隔1秒将系统时间写入文件time.log

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <time.h>

int main()
{
	pid_t pid;
	FILE *fp;
	time_t t;
	int i;

	if((pid = fork())< 0)
	{
		perror("fork");
		exit(-1);
	}
	if(pid > 0)
	{
		exit(0);
	}
	else if(pid == 0)
	{
		if(setsid()< 0)//创建一个新的会话
		{
			perror("setsod");
			exit(-1);
		}
		else
		{
			chdir("/tmp");//tmp目录可读可写可执行
			if(umask(0) < 0)//重设文件权限掩码
			{
				exit(-1);
			}
			else
			{
				for(i=0;i<getdtablesize();i++)
				{
					close(i);
				}
			}
			while(1)
			{
				fp = fopen("time.log","a+");
				time(&t);
				fprintf(fp,"%s",ctime(&t));
				fflush(fp);
				sleep(1);
			}
		}
	}
}

setsid函数:
pid_t setsid(void);
成功:返回调用进程的会话ID;失败:-1,设置errno。
调用了setsid函数的进程,既是新的会长,也是新的组长
getsid函数
pid_t getsid(pid_t pid)
成功:返回调用进程的会话ID;失败:-1,设置errno
1.pid为0表示察看当前进程session ID
2.ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
3.组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

getpid:pid_t getpid(void); 获取进程id
getpgid:pid_t getpgid(pid_t pid); 获取进程组id

更简便地创建守护进程: nohup 命令
nohup xxxx &

七、GDB 调试多进程程序

set follow-fork-mode child 设置GDB调试子进程
set follow-fork-mode parent 设置GDB调试父进程
set detach-on-fork on/off 设置GDB跟踪调试单个进程或多个
on: 只调试父进程或子进程的其中一个,(根据follow-fork-mode来决定),这是默认的模式
off:父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定),另一个进程会被设置为暂停状态。
info inferiors 显示GDB调试的进程
inferiors 进程序号(1,2,3…) 切换GDB调试的进程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值