文章目录
前言
并发是指在操作系统中,同一时间段内有多个进程都处于已启动运行到运行完毕之间的状态,但在任意时刻点上仍只有一个进程在运行
单道程序设计是指所有进程排队执行,若A阻塞则B只能等待
多道程序设计是指在计算机内存中同时存在几道相互独立的程序,它们在管理程序控制之下,相互穿插运行,多道程序设计的实现依赖于硬件时钟信号,即时钟中断,时钟中断有硬件基础作为保障,对进程而言不可抗拒,操作系统中的中断处理函数来负责调度程序执行。
内核吧进程列表存放在叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct的结构,该结构被称为进程描述符,其定义在<linux/sched.h>中。
struct task_struct{
pid_t pid;
unsigned long state;
进程切换时需要保存和恢复的CPU寄存器;
描述虚拟地址空间的信息;
描述控制终端的信息;
当前工作目录;
umask掩码;
文件描述符表;
信号相关的信息;
用户id和组id;
会话;
进程可使用的资源上限;
...
}
fork()函数
函数解析
#include<unistd.h>
pid_t fork(void);
DESCRIPTION
fork() creates a new process by duplicating the calling process. The
new process is referred to as the child process. The calling process
is referred to as the parent process
RETURN VALUE
On success, the PID of the child process is returned in the parent, and
0 is returned in the child. On failure, -1 is returned in the parent,
no child process is created, and errno is set appropriately.
Notice:
fork()创建子进程后,遵循读时共享写时复制的原则;
fork()结束后:
父子进程相同的内容包括:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...(0-3G内存空间)
父子进程不同之处在于:进程ID、fork()返回值、父进程ID、进程运行时间、定时器、未决信号集
fork()系统调用从内核返回两次,一次回到父进程,另一次回到新产生的子进程,父子进程执行的先后顺序取决于内核所使用的调度算法,理论上是随机的
循环创建(2^n)-1个进程
pid_t pid;
for(int i = 0; i < n; i++)
{
pid = fork();
if(-1 == pid)
{
perror("fork error");
exit(1);
}
else if(0 == pid)
{
printf("this is child process, pid = %u, ppid = %u\n", getpid(),getppid());
}
else
{
sleep(1);
}
}
循环创建n个进程
pid_t pid;
for(int i = 0; i < 5; i++)
{
pid = fork();
if(-1 == pid)
{
perror("error occurs when fork");
exit(1);
}
else if(0 == pid)
{
printf("this is the %d child process, pid = %u, ppid = %u\n",i, getpid(), getppid());
break;
}
else
{
sleep(1);
}
}
gdb调试跟踪父子进程
set follow-fork-mode parent
set follow-fork-mode child
exec函数族
函数解析
#include <unistd.h>
extern char **environ; //环境变量表,用于execle()及execvpe()
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
DESCRIPTION
The const char *arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as
arg0, arg1, ..., argn. Together they describe a list of one or more pointers to null-terminated strings that
represent the argument list available to the executed program. The first argument, by convention, should point
to the filename associated with the file being executed. The list of arguments must be terminated by a null
pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL.
The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that rep‐
resent the argument list available to the new program. The first argument, by convention, should point to the
filename associated with the file being executed. The array of pointers must be terminated by a null pointer.
RETURN VALUE
The exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate
the error.
Notice:
exec()函数族在fork()完成后可以创建新的地址空间,并将新的程序载入其中,当进程调用一种exec()函数时该进程的用户空间代码
和数据将完全被新程序替换,从新程序的启动例程开始执行,调用exec()函数并不产生新进程
示例:
pid = fork();
if(-1 == pid)
{
perror("error occurs when fork");
exit(1);
}
else if(0 == pid)
{
execlp("ls","ls -l -a","-l","-a",NULL);
execl("/bin/ls","ls -l -a","-l","-a",NULL);
/*
char* argv[] = {"ls","ls -l -a","-l","-a",NULL};
execv("/bin/ls",argv);
*/
}
else
{
printf("this is the parent thread");
sleep(1);
}
wait()函数族
在进行wait()函数族之前,先区分两个概念:僵尸进程和孤儿进程
僵尸进程:进程终止,父进程尚未及时回收,子进程残留资源(PCB)存放于内核中,成为僵尸进程。
孤儿进程:父进程先于子进程结束,子进程变为孤儿进程,之后孤儿进程的父进程编程init进程,这一过程成为init进程领养孤儿进程
函数解析
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);/*1.阻塞等待子进程退出 2.回收子进程残留资源 3.获取子进程退出状态*/
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);/* This function
is the glibc and POSIX interface; see NOTES for information on the raw system call. */
DESCRIPTION
The wait() system call suspends execution of the calling process until one of its children terminates. The call
wait(&status) is equivalent to:
waitpid(-1, &status, 0);
The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed
state. By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument,
as described below.
The value of pid can be:
<-1 meaning wait for any child process whose process group ID is equal to the absolute value of pid.
-1 meaning wait for any child process.
0 meaning wait for any child process whose process group ID is equal to that of the calling process.
>0 meaning wait for the child whose process ID is equal to the value of pid.
The value of options is an OR of zero or more of the following constants:
WNOHANG return immediately if no child has exited.
WUNTRACED also return if a child has stopped (but not traced via ptrace(2)).
Status for traced children which have stopped is provided even if this option is not specified.
WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery of SIGCONT.
If status is not NULL, wait() and waitpid() store status information in the int to which it points. This integer
can be inspected with the following macros (which take the integer itself as an argument, not a pointer to it, as
is done in wait() and waitpid()!):
WIFEXITED(status)
returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning
from main().
WEXITSTATUS(status)
returns the exit status of the child. This consists of the least significant 8 bits of the status argu‐
ment that the child specified in a call to exit(3) or _exit(2) or as the argument for a return statement
in main(). This macro should be employed only if WIFEXITED returned true.
WIFSIGNALED(status)
returns true if the child process was terminated by a signal.
WTERMSIG(status)
returns the number of the signal that caused the child process to terminate. This macro should be
employed only if WIFSIGNALED returned true.
WIFSTOPPED(status)
returns true if the child process was stopped by delivery of a signal; this is possible only if the call
was done using WUNTRACED or when the child is being traced (see ptrace(2)).
WSTOPSIG(status)
returns the number of the signal which caused the child to stop. This macro should be employed only if
WIFSTOPPED returned true.
RETURN VALUE
wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.
waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and
one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. On error, -1
is returned.
进程组
进程组也称为作业,BSD于1980年前后向UNIX中增加的一个特性,代表一个或多个进程的集合,每个进程都会属于一个进程组,操作系统设计使用进程组的特性,是为了简化对多个进程的管理。
当父进程创建一个子进程的时候,默认子进程与父进程属于同一个进程组,进程组ID等于第一个进程ID(组长进程),所以,组长进程标识:其进程组ID等于其进程ID。
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止,只要进程组中有一个进程存在,进程组就存在,与其组长进程是否终止无关。
组长进程的生命周期:从进程组创建到最后一个进程终止。
一个进程可以为自己或子进程设置进程组ID。
进程组操作函数
getpgrp()
#include <unistd.h>
pid_t getpgrp(void); /* POSIX.1 version */
pid_t getpgrp(pid_t pid); /* BSD version *
RETURN VALUE
getpgid(), and the BSD-specific getpgrp() return a process group on success. On error, -1
is returned, and errno is set appropriately.
获取当前进程的进程组ID
setpgrp()
#include <unistd.h>
int setpgrp(void); /* System V version */
int setpgrp(pid_t pid, pid_t pgid); /* BSD version */
RETURN VALUE
On success, setpgid() and setpgrp() return zero. On error, -1 is returned, and errno is set
appropriately.
设置当前进程的进程组ID
getpgid()
#include <unistd.h>
pid_t getpgid(pid_t pid);
RETURN VALUE
getpgid(), and the BSD-specific getpgrp() return a process group on success. On error, -1
is returned, and errno is set appropriately.
获取指定进程的进程组ID,如果pid=0,那么该函数作用和getpgrp()一样。
setpgid()
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
RETURN VALUE
On success, setpgid() and setpgrp() return zero. On error, -1 is returned, and errno is set
appropriately.
NOTICE
权级问题:非root进程只能改变自己创建的子进程,或有权限操作的进程
改变进程默认所属的进程组。通常可用来加入一个进程组或创建一个新进程组。
进程组操作函数示例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
int main(void)
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fork fail");
exit(1);
}
else if(pid == 0)
{
printf("This is child process, pid == %d, child group ID == %d",getpid(),getpgid(0));
sleep(3);
printf("This is child process, pid == %d, child group ID == %d",getpid(),getpgid(0));
exit(0);
}
else
{
sleep(1);
setpgid(pid,pid); //为子进程设立单独的进程组
sleep(5);
printf("This parent process, process ID = %d, group ID = %d", getpid(), getpgid(0));
sleep(7);
setpgid(getpid(),getppid());
printf("This parent process, process ID = %d, group ID = %d", getpid(), getpgid(0));
}
}
会话
会话代表一个或多个进程组的集合
创建一个会话需注意以下6点:
- 调用进程不能是进程组组长,该进程会变成新会话首进程(session header);
- 该进程成为一个新进程组的组长进程;
- 需要有root权限(Ubuntu不需要);
- 新会话丢弃原有会话控制终端(shell, keyboard…),该会话没有控制终端;
- 该调用进程是组长进程,则出错返回;
- 建立新会话时应先调用fork,终止父进程,子进程调用setsid。
会话操作函数
#include <unistd.h>
pid_t setsid(void);
DESCRIPTION
setsid() creates a new session if the calling process is not a process group leader. The
calling process is the leader of the new session(i.e., its session ID is made the same as
its process ID). The calling process also becomes the process group leader of a new process
group in the session (i.e., its process group ID is made the same as its process ID).
The calling process will be the only process in the new process group and in the new session.
The new session has no controlling terminal.
RETURN VALUE
On success, the (new) session ID of the calling process is returned. On error, (pid_t) -1
is returned, and errnois set to indicate the error.
#include <unistd.h>
pid_t getsid(pid_t pid);
DESCRIPTION
getsid(0) returns the session ID of the calling process. getsid(p) returns the session ID
of the process with process ID p. (The session ID of a process is the process group ID of
the session leader.)
RETURN VALUE
On success, a session ID is returned. On error, (pid_t) -1 will be returned, and errno is
set appropriately.
示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
int main(void)
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fork fail");
exit(1);
}
else if(pid == 0)
{
printf("This is child process, pid == %d, child process group ID == %d, child process session ID == %d\n",getpid(),getpgid(0), getsid(0));
sleep(3);
setsid(); //子进程非组长进程,故可使其成为新会话首进程,且成为组长进程
printf("This is child process, pid == %d, child process group ID == %d, child process session ID == %d\n",getpid(),getpgid(0), getsid(0));
exit(0);
}
return 0;
}
守护进程
守护进程又称精灵(daemon)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,一般采用d结尾。
Linux后台的一些系统服务进程,没有控制终端,不能和用户直接交互,不受用户登录及注销的影响,一直在运行着,他们都是守护进程,创建守护进程的第一步是调用setsid()创建一个新的session,并成为session header。
创建守护进程模型
- 创建子进程,父进程退出,所有工作在子进程中进行,形式上脱离了控制终端;
- 在子进程中创建新会话,使子进程完全独立出来,脱离控制;
- 使用chdir(),改变当前目录为根目录,防止占用可卸载的文件系统,也可切换到其他目录 ;
- 使用umask(),重设文件权限掩码,防止继承的文件创建屏蔽字拒绝某些权限;
- 使用dup2(),将STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO重定向到/dev/null;
- 守护进程主逻辑。
- 特殊的退出逻辑
示例
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(void)
{
pid_t pid,sid;
int fd;
pid = fork();
if(pid<0)
{
perror("fork error");
exit(1);
}
if(pid>0)
{
return 0;
}
else
{
sid = setsid();
if(chdir("/home/daemon")<0)
{
perror("change directory fail");
exit(1);
}
umask(0002);
close(0);
fd = open("/dev/null"); //此时文件描述符中最小的为0,即fd=0
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
{
//主逻辑
}
{
//退出逻辑
signal();
}
}
}