Linux进程管理

一、进程

1.概念

  • 进程是正在内存中运行的程序
  • ps au可以列出操作系统中正在运行的进程

2.fork函数

#include<unistd.h>
// 功能:创建进程
// 返回值:成功时返回进程ID,失败时返回-1
pid_t fork();
  • 两个进程都将执行fork函数返回后的语句。因为通过同一个进程、复制相同的内存空间,所以之后的程序流要通过fork函数的返回值加以区分。对于父进程,fork函数返回子进程ID;对于子进程,fork函数返回0
#include<stdio.h>
#include<unistd.h>

int gval = 10;
int main() {
    pid_t pid;
    int lval = 20;
    gval++; lval += 5;

    pid = fork();
    if (pid == 0) {
        gval += 2, lval += 2;
        printf("Child process: gval=%d, lval=%d \n", gval, lval);
    }
    else {
        gval -= 2, lval -= 2;
        printf("Parent process: gval=%d, lval=%d \n", gval, lval);
    }

    return 0;
}
  • 运行结果:

Parent process: gval=9, lval=23 
Child process: gval=13, lval=27 

3.僵尸进程

  • 进程完成工作后(执行完main函数中的程序后)应被销毁,但又是这些进程会变成僵尸进程,占用系统的重要资源
  • 产生僵尸进程的原因:1)传递参数并调用exit函数;2)main函数中执行return语句并返回值。向exit函数传递的参数值和return返回值都会传递给操作系统,而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处于这种状态下的进程就是僵尸进程。将子进程变成僵尸进程的正是操作系统。
  • 操作系统不会主动把这些值传递给父进程那个,除非父进程主动发起请求
#include<stdio.h>
#include<unistd.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        printf("Child process id: %d \n", getpid());
    }
    else {
        printf("Parent process id: %d \n", getpid());
        sleep(10);
    }
    return 0;
}

运行结果:可以看到子进程(PID=3475)的状态为Z+,说明已经变成僵尸进程

4.利用wait函数销毁僵尸进程

#include<sys/wait.h>
// 功能:主动请求获取子进程的返回值
// 返回值:成功时返回终止的子进程ID,失败时返回-1
pid_t wait(int* statloc);
  • 调用用此函数时,如果有子进程终止,那么子进程终止时传递的返回值将保存到该函数的参数所指内存空间,但函数参数所指向的单元还包含其他信息,所以需要通过以下宏进行分离:WIFEXITED子进程正常终止时返回true;WEXITSTATUS返回子进程的返回值。
  • 调用此函数时,如果没有子进程终止,程序将阻塞直到有子进程终止。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main() {
    int status;
    pid_t pid = fork();
    if (pid == 0) {
        printf("First child process id: %d \n", getpid());
        return 3;
    }
    else {
        pid = fork();
        if (pid == 0) {
            printf("Second child process id: %d \n", getpid());
            exit(7);
        }
        else {
            printf("Parent process id: %d \n", getpid());
            wait(&status);
            if (WIFEXITED(status))
                printf("first return value form child: %d \n", WEXITSTATUS(status));

            wait(&status);
            if (WIFEXITED(status))
                printf("second return value form child: %d \n", WEXITSTATUS(status));

            sleep(10);
        }
    }
    return 0;
}

运行结果:两个子进程的状态均不为Z+

5.利用waitpid函数销毁僵尸进程

#include<sys/wait.h>
// 功能:请求获取指定子进程的返回值
// 参数:
//    pid--等待终止的子进程的ID,若传递-1,则与wait函数相同,可以等待任意子进程终止
//    statloc--保存子进程返回值的变量地址
//    options--传递头文件sys/wait.h中声明的常量WNOHANG,即使没有终止的子进程也不会进入阻塞状态
// 返回值:成功时返回终止的子进程ID(或0),失败时返回-1
pid_t waitpid(pid_t pid,int* statloc,int options);
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(int argc, char* argv[]) {
    int status;
    pid_t pid = fork();
    if (pid == 0) {
        sleep(15);
        return 24;
    }
    else {
        while (!waitpid(-1, &status, WNOHANG)) {
            sleep(3);
            puts("sleep 3s");
        }
        if (WIFEXITED(status))
            printf("child process return value: %d \n", WEXITSTATUS(status));

    }
    return 0;
}

运行结果:输出了5次"sleep 3s",说明waitpid函数并未阻塞

二、信号处理机制

1.signal函数

#include<signal.h>
// 功能:信号注册
// 参数:
//    signo--信号id
//    void(*func)(int)--处理函数
// 返回值:返回注册的函数指针
void (*signal(int signo, void(*func)(int)))(int);
  • SIGALRM--已到通过调用alarm函数注册的时间
  • SIGINT--输入CTRL+C
  • SIGCHLD--子进程终止
#include<unistd.h>
// 功能:指定一段时间后产生SIGALRM信号,若传递0,则之前对SIGALRM信号的预约取消
// 参数:
//    seconds--定时
// 返回:返回0或以秒为单位的距SIGALRM信号发生所剩时间
unsigned int alarm(unsigned int seconds);
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void timeout(int sig) {
    if (sig == SIGALRM)
        puts("Time out!");
    alarm(2);
}

void keycontrol(int sig) {
    if (sig == SIGINT)
        puts("CTRL+C pressed");
}

int main(int argc, char* argv[]) {
    signal(SIGALRM, timeout);
    signal(SIGINT, keycontrol);
    alarm(2);

    for (int i = 0; i < 3; i++) {
        puts("wait...");
        sleep(10);
    }

    return 0;
}

运行结果:

2.sigaction函数

  • signal函数在UNIX系列的不同操作系统中可能存在区别,但sigaction函数完全相同
#include<signal.h>
// 功能:信号注册函数
// 参数:
//    signo--信号信息
//    act--对应于第一个参数的信号处理函数(信号处理器)信息
//    oldact--通过此参数获取之前注册的信号处理函数指针,若不需要则传递0
// 返回值:成功时返回0,失败时返回-1
int sigaciton(int signo,const struct sigaciton* act,struct sigacion* oldact);
  • sigaction结构体
struct sigaciton{
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
}
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void timeout(int sig) {
    if (sig == SIGALRM)
        puts("Time out!");
    alarm(2);
}

int main(int argc, char* argv[]) {
    struct sigaction act;
    act.sa_handler = timeout;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGALRM, &act, 0);

    alarm(2);

    for (int i = 0; i < 3; i++) {
        puts("wait...");
        sleep(10);
    }

    return 0;
}

运行结果:

三、利用信号处理机制消除僵尸进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>

void read_childproc(int sig) {
    int status;
    pid_t id = waitpid(-1, &status, WNOHANG);
    if (WIFEXITED(status)) {
        printf("Removed proc id: %d \n", id);
        printf("Child return value: %d \n", WEXITSTATUS(status));
    }
}

int main(int argc, char* argv[]) {
    pid_t pid;
    struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGCHLD, &act, 0);

    pid = fork();
    if (pid == 0) {
        puts("Child process");
        sleep(10);
        return 12;
    }
    else {
        printf("Child proc id: %d \n", pid);
        pid = fork();
        if (pid == 0) {
            puts("Child process");
            sleep(10);
            exit(24);
        }
        else {
            printf("Child proc id: %d \n", pid);
            for (int i = 0; i < 5; i++) {
                puts("wait...");
                sleep(5);
            }
        }
    }
    return 0;
}

四、进程间通信

1.管道通信

  • 管道并非属于进程的资源,和套接字一样属于操作系统,也就不是fork函数复制的对象
#include<unistd.h>
// 功能:创建管道
// 参数:
//    filedes[0]--管道出口
//    filedes[1]--管道入口
// 返回值:成功时返回0,失败时返回-1
int pipe(int filedes[2]);
#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30

int main(int argc,char* argv[]){
	int fds[2];
	char str[]="Hello world";
	char buf[BUF_SIZE];
	pid_t pid;

	pipe(fds);
	pid = fork();
	if(pid==0){
		write(fds[1],str,sizeof(str));	
	}else{
		read(fds[0],buf,BUF_SIZE);
		puts(buf);
	}
	return 0;	
}

运行结果:输出"Hello world"

3.一个管道双向通信

#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30

int main(int argc, char* argv[]) {
    int fds[2];
    char str1[] = "C";
    char str2[] = "C++";
    char buf[BUF_SIZE];
    pid_t pid;

    pipe(fds);
    pid = fork();
    if (pid == 0) {
        write(fds[1], str1, sizeof(str1));
        sleep(2);// 向管道传递数据时,先读取数据的进程将得到数据,删除会导致父进程读不到数据
        read(fds[0], buf, BUF_SIZE);
        printf("Child proc output: %s \n", buf);
    }
    else {
        read(fds[0], buf, BUF_SIZE);
        printf("Parent proc output: %s \n", buf);
        write(fds[1], str2, sizeof(str2));
        sleep(3);	// 父进程先终止时会弹出命令提示符,但子进程还在运行,删除的话不会有问题
    }
    return 0;
}

运行结果:

Parent proc output: C 
Child proc output: C++ 

4.两个管道双向通信

#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30

int main(int argc, char* argv[]) {
    int fds1[2];	// 父写子读
    int fds2[2];	// 父读子写
    char str1[] = "C";
    char str2[] = "C++";
    char buf[BUF_SIZE];
    pid_t pid;

    pipe(fds1);
    pipe(fds2);
    pid = fork();
    if (pid == 0) {
        write(fds2[1], str1, sizeof(str1));
        sleep(2);// 向管道传递数据时,先读取数据的进程将得到数据,删除会导致父进程读不到数据
        read(fds1[0], buf, BUF_SIZE);
        printf("Child proc output: %s \n", buf);
    }
    else {
        read(fds2[0], buf, BUF_SIZE);
        printf("Parent proc output: %s \n", buf);
        write(fds1[1], str2, sizeof(str2));
        sleep(3);	// 父进程先终止时会弹出命令提示符,但子进程还在运行,删除的话不会有问题
    }
    return 0;
}

运行结果:

Parent proc output: C 
Child proc output: C++

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值