Linux —— 进程控制

目录

一,进程创建

写时拷贝

二,进程终止

三,进程等待

获取子进程status

四,进程程序替换

替换函数

五,手写简单的shell


一,进程创建

  • 命令行启动命令(程序、指令等);
  • 通过程序自身fork创建;
#include
//子进程返回0,父进程返回子进程的ID,出错返回-1
pid_t fork(void);

进程调用fork,当控制转移到内核中的fork代码后,内核将会:

  • 分配新的内存块和内核数据结构给子进程;
  • 拷贝父进程部分数据结构内容给子进程;
  • 添加子进程到系统进程列表中;
  • fork返回,开始调度器调度;

Linux —— 进程控制_第1张图片

#include 
#include 
#include 

int main()
{
  pid_t pid;
  printf("Before: pid is %d\n", getpid());
  pid = fork();
  if(pid == -1)
  {
    perror("fork");
    exit(-1);
  }
  printf("After: pid is %d, fork return %d\n", getpid(), pid);
  sleep(1);
  return 0;                                                                 
}
[wz@192 Desktop]$ ./target 
Before: pid is 21739
After: pid is 21739, fork return 21740
After: pid is 21740, fork return 0
  • fork前,父进程独立执行;
  • fork后,父子进程分流执行;父子谁先执行完全由调度器决定;

写时拷贝

        通常父子代码共享,数据也共享(父子在不写入时);当有任意一方写入时,便以写实拷贝的方式各自一份副本,从而保证父子进程的独立性;

Linux —— 进程控制_第2张图片

二,进程终止

  • 代码运行结束,结果正确,退出码为0;
  • 代码运行结束,结果不正确,退出码非0;
  • 代码异常终止;
#include 
#include 
#include 
int main()
{
  int i=0;
  for(i;i<200;i++)
  {
    printf("%d:%s\n",i,strerror(i));
  }
  return 0;                                                               
}
[wz@192 Desktop]$ ./target 
0:Success
1:Operation not permitted
2:No such file or directory
3:No such process
4:Interrupted system call
5:Input/output error
6:No such device or address
7:Argument list too long
8:Exec format error
9:Bad file descriptor
10:No child processes
11:Resource temporarily unavailable
12:Cannot allocate memory
13:Permission denied
14:Bad address
15:Block device required
16:Device or resource busy
17:File exists
18:Invalid cross-device link
19:No such device
20:Not a directory
21:Is a directory
22:Invalid argument
23:Too many open files in system
24:Too many open files
25:Inappropriate ioctl for device
26:Text file busy
27:File too large
28:No space left on device
29:Illegal seek
30:Read-only file system
31:Too many links
32:Broken pipe
33:Numerical argument out of domain
34:Numerical result out of range
35:Resource deadlock avoided
36:File name too long
37:No locks available
38:Function not implemented
39:Directory not empty
40:Too many levels of symbolic links
41:Unknown error 41
42:No message of desired type
43:Identifier removed
44:Channel number out of range
45:Level 2 not synchronized
46:Level 3 halted
47:Level 3 reset
48:Link number out of range
49:Protocol driver not attached
50:No CSI structure available
51:Level 2 halted
52:Invalid exchange
53:Invalid request descriptor
54:Exchange full
55:No anode
56:Invalid request code
57:Invalid slot
58:Unknown error 58
59:Bad font file format
60:Device not a stream
61:No data available
62:Timer expired
63:Out of streams resources
64:Machine is not on the network
65:Package not installed
66:Object is remote
67:Link has been severed
68:Advertise error
69:Srmount error
70:Communication error on send
71:Protocol error
72:Multihop attempted
73:RFS specific error
74:Bad message
75:Value too large for defined data type
76:Name not unique on network
77:File descriptor in bad state
78:Remote address changed
79:Can not access a needed shared library
80:Accessing a corrupted shared library
81:.lib section in a.out corrupted
82:Attempting to link in too many shared libraries
83:Cannot exec a shared library directly
84:Invalid or incomplete multibyte or wide character
85:Interrupted system call should be restarted
86:Streams pipe error
87:Too many users
88:Socket operation on non-socket
89:Destination address required
90:Message too long
91:Protocol wrong type for socket
92:Protocol not available
93:Protocol not supported
94:Socket type not supported
95:Operation not supported
96:Protocol family not supported
97:Address family not supported by protocol
98:Address already in use
99:Cannot assign requested address
100:Network is down
101:Network is unreachable
102:Network dropped connection on reset
103:Software caused connection abort
104:Connection reset by peer
105:No buffer space available
106:Transport endpoint is already connected
107:Transport endpoint is not connected
108:Cannot send after transport endpoint shutdown
109:Too many references: cannot splice
110:Connection timed out
111:Connection refused
112:Host is down
113:No route to host
114:Operation already in progress
115:Operation now in progress
116:Stale file handle
117:Structure needs cleaning
118:Not a XENIX named type file
119:No XENIX semaphores available
120:Is a named type file
121:Remote I/O error
122:Disk quota exceeded
123:No medium found
124:Wrong medium type
125:Operation canceled
126:Required key not available
127:Key has expired
128:Key has been revoked
129:Key was rejected by service
130:Owner died
131:State not recoverable
132:Operation not possible due to RF-kill
133:Memory page has hardware error
134:Unknown error 134
135:Unknown error 135
136:Unknown error 136
137:Unknown error 137
138:Unknown error 138

进程常见退出方法:

  • 正常终止,可通过 echo $? 查看最近一次执行程序退出码;
    • main返回,return 0代表进程退出,0退出码表示成功;给系统查看,以确认进程是否正确;非main函数的return不受终止进程,是结束函数;
    • exit,任意位置调用此函数,都会直接终止进程;
    • _exit,与exit类型,但此函数不会刷新缓冲区等处理工作,直接终止进程;
  • 异常退出;
    • ctrl + C,信号终止;

退出码可自定义,也可使用系统错误码;

#include 
void exit(int status);
#include 
void _exit(int status);
//status 定义了进程终止状态,父进程通过wait来获取该值;
//虽然status为int,但仅低8位可被父进程使用,_exit(-1)执行echo $?结果为255;

Linux —— 进程控制_第3张图片

#include 
#include 
int main()
{
  printf("hello");
  exit(0);                                                                  
  return 0;
}
[wz@192 Desktop]$ ./target 
hello[wz@192 Desktop]$ 
#include 
#include 
int main()
{
  printf("hello");
  _exit(0);                                                                  
  return 0;
}
[wz@192 Desktop]$ ./target 
[wz@192 Desktop]$ 

return退出

        一种常见的退出进程方法,执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当作exit的参数;

进程终止的核心思想,就是归还资源;

  • 释放曾经为管理进程所维护的所有数据结构对象;
    • 不是真的把数据结构对象销毁,而是设置为不用状态,保存起来,“数据结构池”;
  • 释放程序代码和数据所占的内存空间;
    • 不是将代码和数据清空,把内存设置为无效即可;
  • 取消曾经该进程的链接关系;

三,进程等待

        进程等待即将当前进程放入等待队列,并将进程状态设置为非R状态;唤醒进程即将进程从等待队列中移入运行队列,并将进程设置为R状态;

Linux —— 进程控制_第4张图片

进程等待的必要性

  • 子进程退出,父进程如不管不顾,就可能造成僵死进程,进而造成内存泄露;
  • 进程进入僵死状态,将无能为力,即使是kill -9,因为无法杀死一个已经死亡的进程;
  • 父进程交派给子进程的任务完成状况需知道,如子进程运行完,其结果对错与否,是否正常退出;
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息;
  • 尽量父进程要晚于子进程退出,可规格化进行资源回收;

pid_t wait(int*status)

  • 返回值,成功,返回被等待进程pid;失败,返回-1;
  • 参数,获取子进程退出状态,不关心可设置为NULL;
#include
#include

pid_t wait(int *status);
#include 
#include 
#include 
#include 
#include 
int main()
{
    int i=0;
    //创建5个子进程,睡眠2秒退出 
    for(i=0; i<5; i++)
    {
        pid_t id = fork();
        if(id<0)
        {                                                                          
            perror("fork");
            return 1;
        }
        else if(id==0)
        {
            sleep(2);
            printf("child:pid=%d, ppid=%d, id=%d\n",getpid(),getppid(),id);
            exit(1);
        }
    }
    //父进程等待
    for(i=0; i<5; i++)
    {
        sleep(3);
        pid_t wait_pid = wait(NULL);
        printf("father:pid=%d, wait_pid=%d\n",getpid(), wait_pid);
    }
    sleep(3);
    printf("father finish!\n");
    return 0;
}
[wz@192 Desktop]$ ./target 
child:pid=116610, ppid=116606, id=0
child:pid=116607, ppid=116606, id=0
child:pid=116608, ppid=116606, id=0
child:pid=116609, ppid=116606, id=0
child:pid=116611, ppid=116606, id=0
father:pid=116606, wait_pid=116607
father:pid=116606, wait_pid=116608
father:pid=116606, wait_pid=116609
father:pid=116606, wait_pid=116610
father:pid=116606, wait_pid=116611
father finish!

pid_ t waitpid(pid_t pid, int *status, int options)

  • 返回值
    • 正常返回的时候,waitpid返回收集到的子进程的进程PID;
    • 如设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,返回0;
    • 如调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
  • 参数
    • pid
      • -1等待任一个子进程,与wait等效;
      • >0等待指定pid子进程;
    • status
      • WIFEXITED(status),若为正常终止子进程返回的状态,则为真;
        • WEXITSTATUS(status),若WIFEXITED非零,提取子进程退出码;
      • WIFSIGNALED(status),若子进程被信号所终止,返回真;
        • WTERMSIG(status),返回信号终止进程的信号值;
    • options (阻塞,调用方在等待,非阻塞,非阻塞轮询方案)
      • 0,阻塞调用;
      • WNOHANG,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待;
        • hang挂起宕机了;
#include
#include

pid_ t waitpid(pid_t pid, int *status, int options);
#include 
#include 
#include 
#include 
#include 
int main()
{
    //创建子进程,睡眠2秒退出 
    pid_t id = fork();
    if(id<0)
    {                                                                          
        perror("fork");
        return 1;
    }
    else if(id==0)
    {
        sleep(2);
        printf("child:pid=%d, ppid=%d, id=%d\n",getpid(),getppid(),id);
        exit(1);
    }
    //父进程等待
    sleep(5);
    pid_t wait_pid = waitpid(id, NULL, 0);
    printf("father:pid=%d, wait_pid=%d\n",getpid(), wait_pid);
    sleep(3);
    printf("father finish!\n");
    return 0;
}
  • 若子进程已退出,调用wait/waitpid时,wait/waitpid会立即返回,并释放资源,获得子进程退出信息;
  • 如在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞;
  • 如不存在该子进程,则立即出错返回;

Linux —— 进程控制_第5张图片

获取子进程status

  • wait/waitpid都有一个status参数,此参数为输出型参数,由操作系统填充;
  • 如传递NULL,表示不关心子进程的退出状态信息;否则,操作系统会根据该参数,将子进程发退出信息反馈给父进程;
  • status不能简单的当作整型看待,可当作位图,只研究低16位;

Linux —— 进程控制_第6张图片

#include 
#include 
#include 
#include 
#include 
int main()
{
    //创建子进程,睡眠2秒退出 
    pid_t id = fork();
    if(id<0)
    {                                                                          
        perror("fork");
        return 1;
    }
    else if(id==0)
    {
        sleep(5);
        printf("child:pid=%d, ppid=%d, id=%d\n",getpid(),getppid(),id);
        exit(1);
    }
    //父进程等待
    int status = 0;
    pid_t wait_pid = waitpid(id, &status, 0);
    printf("father:pid=%d, wait_pid=%d\n",getpid(), wait_pid);
    int stat = (status>>8) & 0xFF; //status二进制中8~15位;
    int sig = status & 0x7F; //status二进制中0~7位;
    sleep(3);
    printf("father finish!\n");
    return 0;
}

 注:

  • 不可通过设置全局变量,来告知父进程子进程的退出状态,因为父子进程相互独立,且写实拷贝;
  • waitpid获取的status值,是从子进程的task_struct中得到的;
  • 一般子进程提前终止,是该进程收到了OS发送的信号(信号中没有0,0就表示为正常终止,正常终止是没有收到任何退出信号 );
[wz@192 Desktop]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX
int status = 0;
pid_t wait_pid = waitpid(id, &status, 0); //阻塞调用
printf("father:pid=%d, wait_pid=%d\n",getpid(), wait_pid);
if(wait_pid>0)
{
    if(WIFEXITED(status))
        printf("child exit normal, status=%d\n", WEXITSTATUS(status));
    else
        printf("child exit error, sig=%d\n", WTERMSIG(status));
}  
int status = 0;
pid_t wait_pid = 0;
do
{
    wait_pid = waitpid(id, &status, WNOHANG); //非阻塞调用
    if(wait_pid == 0)
        printf("child is running\n");
    sleep(1);
}while(wait_pid == 0)

if(WIFEXITED(status) && wait_pid==pid)
    printf("child exit normal, status=%d\n", WEXITSTATUS(status));
else
    printf("child exit error, sig=%d\n", WTERMSIG(status));

四,进程程序替换

        替换原理,用fork创建子进程后执行的是和父进程相同的程序(可能执行分流),子进程往往要调用一种exec函数以执行另一个程序;当调用exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行;调用exec并不创建新进程,exec前后进程id不变;

Linux —— 进程控制_第7张图片

创建子进程的目的

  • 执行父进程的部分代码;
  • 执行其他进程的代码(程序替换);

替换函数

//六种exec开头的替换函数
//调用成功,则加载新的程序,从启动代码开始执行,不返回;
//调用出错,则返回-1;
#include 
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

Linux —— 进程控制_第8张图片

char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};

execl("/bin/ps", "ps", "-ef", NULL);
execlp("ps", "ps", "-ef", NULL);
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
execvp("ps", argv);
execve("/bin/ps", argv, envp);

五,手写简单的shell

Linux —— 进程控制_第9张图片

流程:

  • 获取命令;
  • 解析命令;
  • 建立子进程fork;
  • 替换子进程execvp;
  • 父进程等待子进程的退出wait;
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_CMD 1024
char command[MAX_CMD];

//读取数据,获取命令行
int do_face()
{
    memset(command, 0x00, MAX_CMD); //设置字符数组值为0;
    printf("[myshell]$ ");
    fflush(stdout);
    if(fgets(command, MAX_CMD-1, stdin)) //从输入中读取
    {
        command[strlen(command)-1] = 0; //printf("%s", command);
        return 1;
    }
    return 0;
}

//解析数据,解析命令行
char** do_parse(char* buff)
{
    static char* argv[32];
    int index=0;
    argv[index]=strtok(buff, " "); //printf("index[0]: %s\n", argv[index]);
    while(1)
    {
        index++;
        argv[index]=strtok(NULL, " ");
        if(argv[index]==NULL)
            break;
    }
    return argv;
}

//执行命令,建立子进程、替换子进程,父进程等待子进程退出
int do_exec(char* buff)
{
    char** argv=do_parse(buff);
    if(argv[0]==NULL)
        exit(-1);

    //内置命令
    //如cd应是针对父进程,不可像非内置命令一样使用子进程
    //是shell内的一个函数调用
    if(strcmp(argv[0], "cd")==0 && chdir(argv[1])==0)
        continue;

    //非内置命令
    pid_t pid=fork();
    if(pid==0)
        execvp(argv[0], argv);
    else
    {
        int status;
        int wait_pid=waitpid(-1, &status, 0);
        if(WIFEXITED(status) && wait_pid==pid)
            printf("exit normal, exit code:%d\n", WEXITSTATUS(status));
        else
            printf("exit error\n");
    }
    return 0;
}

int main()
{
    while(1)
    {
        if(do_face()==0)
            continue;
        do_exec(command);
    }
    return 0;
}

你可能感兴趣的:(操作系统,linux)