Linux进程控制

进程创建/等待/终止

进程创建
#include <unistd.h>

pid_t fork(void);
pid_t vfork(void);

pid_t fork(void); 通过复制父进程来创建子进程,子进程因为拷贝了父进程pcb中的很多数据(在内核中调用clone完成),因此与父进程内存指针以及程序计数器相同,所以运行的代码以及运行位置一样(采用写时拷贝技术,提高效率)。
父子进程之间:代码共享,数据独有。
返回值: -1 创建子进程失败;0 对于子进程,返回值为0;>0 对于父进程,返回值是子进程的pid。
pid_t vfork(void);0 创建一个子进程(内核中调用clone完成),但是创建子进程之后父进程会阻塞(挂起),直到子进程退出或者程序替换。创建的子进程与父进程共用同一个虚拟空间(故不需要为子进程创建虚拟地址空间/页表),为了防止调用栈混乱,因此阻塞父进程。

进程等待

父进程等待子进程的退出,获取这个子进程的返回值,并且回收子进程的所有资源,避免产生僵尸进程。

#include <sys/wait.h>

int wait(int *status); // 一个系统调用接口
pid_t waitpid(pid_t pid, int *status, int options);

int wait(int *status); 该接口等待任意一个子进程退出,是一个阻塞接口,若没有子进程则报错,若没有子进程退出则一直等待,子进程的返回值会放到传入的参数status中。

pid_t waitpid(pid_t pid, int *status, int options);
1.可以等待指定pid的子进程退出
2.options若被指定为WNOHANG(默认为阻塞),则将waitpid设置为非阻塞,则通常需要循环处理
3.pid设置值:-1 表示等待任意一个子进程;>0 表示等待指定pid的子进程
返回值:>0 返回的是退出的子进程的pid;=0 没有子进程退出;-1 等待出错。

进程终止

终止场景:异常退出;正常退出,结果符合预期;正常退出,结果不符合预期

#include <stdio.h>
void perror(const char *s); // 打印上次系统调用的错误信息

#include <errno.h>
char *sys_err_list[] / int errno

#include <string.h>
char *strerror(int errnum);  // 获取上一次系统调用的错误

// 获取一个系统调用的错误信息
void perror(char* msg);
char *strerrno(int errno);

如何终止一个进程:

  • 在main函数中return(return时刷新缓冲区)
  • 在任意地方用exit(int statu)接口(库函数接口,头文件stdlib.h)退出前刷新缓冲区
  • 在任意地方用_exit(int statu)接口(系统调用接口)立即退出,释放进程所有资源,对于系统调用来说,不存在上述两种方式所说的缓冲区

模仿编写shell

#include <sys/types.h>
#include <sys/wait.h>
int execvp(const char* file, const char* argv[]);

const char* file是要运行的文件,会在环境变量PATH中查找file执行;
const char* argv[]是一个参数列表,如同在shell中调用程序一样,参数列表为0,1,2,3……因此第0个参数重复一遍;
const char* argv[]最后一个必须是 NULL;
失败返回-1且在当前进程运行, 成功无返回值,直接结束当前进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <ctype.h>
#define N 1024
#define MIX 32

int main()
{
        while (1)
        {
                // 输入提示
                printf("[mini@localhost]# ");
                // 清空缓冲区
                fflush(stdout);
                char buf[N] = {0};
                // 获取输入的字符串,一直接收至遇到换行符
                fgets(buf, N - 1, stdin);
                buf[strlen(buf) - 1] = '\0';
                printf("cmd: [%s]\n", buf);  // 查看数据接收是否正确

                // 解析输入字符串,获取需要运行的程序名称及参数
                int argc = 0;
                char *argv[MIX] = {NULL};  // 指针数组
                char *ptr = buf;
                while (*ptr != '\0')
                {
                        if (!isspace(*ptr))
                        {
                                argv[argc] = ptr;
                                argc++;
                                while (!isspace(*ptr) && *ptr != '\0')
                                        ptr++;
                                *ptr = '\0';
                        }
                        ptr++;
                }
                argv[argc] = NULL;
                int i = 0;
                for (i = 0; i < argc; i++)
                        printf("[%s]\n", argv[i]);  // 查看数据解析是否分段正确
                        // 这里输出加\n为了刷新缓冲区内容,如果用空格则会在程序本次循环退出时才输出这段内容

                // 程序替换,调用子进程
                int pid = fork();
                if (pid == 0) // 子进程
                {
                		if (argv[0] == "cd")  // 单独处理cd指令
                                chdir(argv[1]);  // chdir 是C语言中的一个系统调用函数(同cd)

                        execvp(argv[0], argv);
                        exit(0);
                }
                waitpid(-1, NULL, 0);  // 父进程等待子进程结束
        }
        return 0;
}
// 经测试运行程序在输入指令的时候,backspace键按下会显示^H,无法删除已输入的字符
// 按下delete键会显示^[[3~,同样无法删除已输入的字符

以上代码在运行时如输入pwd,则会显示当前代码所在文件路径,但是运行时输入cd命令依旧无法跳转文件目录。

封装fork/wait等操作

编写函数 process_create(pid_t* pid, void* func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数.

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

void process_create(pid_t* pid, void* func, void* arg)
{
        *pid = fork();
        pid_t id = 0;
        if (*pid < 0)
        {
                perror("fork error");
                return;
        }
        else if (*pid == 0)  // child 子进程运行传入的函数及参数
        {
                // 返回值为int的函数指针function
                // execvp的返回值为int
                // 两个传入的参数先强转为char*再解引用
                ((int(*)())func) (((char**)arg)[0], (char**)arg);
                exit(0);
        }
        else  // father
        {
                while (wait(NULL) != *pid)
                {
                        id = wait(NULL);
                        printf("child id: %d\n", id);
                        return;
                }
        }
        return;
}

int main()
{
        pid_t pid = 0;
        char *arg[] = {"ls", "-l", NULL};
        process_create(&pid, execvp, arg);
        return 0;
}

popen/system与fork

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);  // 调用失败则返回-1

popen()函数是标准c提供的一个管道创建函数,其内部操作主要是创建一个管道,调用fork创建子进程,关闭不需用的文件描述符,调用exec函数族执行popen的第一个参数。然后等到关闭。管道为半双工工作,所以type参数只能定义成只读(r)、只写(w), 不能可读可写,结果流也只读 、只写。
popen()调用fork()产生子进程,子进程中调用/bin/sh -c来执行参数command的指令。这个进程必须由 pclose 关闭。
command参数是字符串指针,指向以NULL结束符结尾的字符串,这个字符串包含一个shell命令,命令被送到 /bin/sh 以 -c 参数执行,即由 shell 来执行。
如果type是“r”则文件指针连接到command的标准输出;如果type是“w”则文件指针连接到command的标准输入。
返回值: 成功则返回文件指针,失败返回NULL,错误原因可由errno查看。
返回值是一个普通的标准I/O流,只能用 pclose() 函数关闭。

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

system()调用fork()产生子进程,子进程调用/bin/sh-c string(/bin/sh 一般是一个软连接,指向某个具体的shell)执行参数字符串所代表的命令,命令执行完后返回原调用的进程。即:fork一个子进程;子进程中调用exec函数执行command;父进程中调用wait等待子进程结束。 调用system()期间SIGCHLD 信号被暂时搁置,SIGINT和SIGQUIT 信号被忽略。
返回值:-1 出现错误;0 调用成功但是没有出现子进程;>0 成功退出的子进程的id。
system()调用/bin/sh时失败返回127,其他失败原因返回-1。若参数为空指针,则返回非零值。
system()调用成功则返回执行shell命令后的返回值,但是此返回值有可能为127,因此最好再检查errno从而确认执行是否成功。
system()继承环境变量,所以在编写具有SUID/SGID权限的程序时不使用system(),以防通过环境变量可能造成的系统安全问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值