一、linux进程结构
- 代码段:存放可执行代码
- 数据段存放程序的全局变量、常量、静态变量
- 堆栈段用于存放动态分配的内存变量
二、创建进程
1.fork
#include<stdio.h>
#include<unistd.h>
pid_t fork(void);
fork有两个返回值
- fork成功调用之后实际分为两个进程,一个是父进程调用fork后返回值是刚才创建的子进程的ID
- 另一个是子进程fork函数的返回值0
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
char *msg;
int k;
printf("process creation study\n");
pid=fork();
switch(pid)
{
case 0:
msg="child process is running";
k=3;
break;
case -1:
perror("process creation failed\n");
break;
default:
msg="parent process is running";
k=5;
break;
}
while(k>0)
{
puts(msg);
sleep(1);
k--;
}
}
- 可以看出父子进程是交替执行的,取决于内核所使用的调度算法
- fork失败的原因是父进程拥有的子进程个数超过了规定的限制
- 操作系统将父进程的内存地址完全复制到子进程
- 子进程继承了父进程的用户ID,组ID、当前目录、根目录、打开的文件,创建文件时使用的屏蔽字、信号屏蔽、上下环境、共享的储存段、资源限制等,不会继承父进程的文件锁,警告
2、孤儿进程
- 一个子进程的父进程先于子进程结束,子进程就变成了孤儿进程,它由init进程收养
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
pid=fork();
switch(pid)
{
case 0:
while(1)
{
printf("A background process,PID:%d\n,parrent ID:%d\n",getpid(),getppid());
sleep(3);
}
case -1:
perror("process failed!\n");
exit(-1);
default:
printf("i am parent process,my pid is %d\n",getpid());
exit(0);
}
}
3、vfork
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int globVar=5;
int main()
{
pid_t pid;
int var =1,i;
//pid=fork();
pid=vfork();
switch(pid)
{
case 0:
i=3;
while(i-->0)
{
printf("child process is running\n");
globVar++;
var++;
sleep(1);
}
printf("child's globVar=%d,var=%d\n",globVar,var);
printf("%s\n",a);
exit(0);
case -1:
perror("process failed\n");
exit(0);
default:
i=5;
while(i-->0)
{
printf("parent process is running\n");
globVar++;
var++;
sleep(1);
}
printf("parent's globVar=%d,var=%d\n",globVar,var);
exit(0);
}
return 0;
}
- vfork创建的子进程共享父进程的地址空间,也就是说父进程完全运用在父进程的地址空间上,子进程对地址的任何数据修改同样为父进程看到
4.守护进程 - 是在后台运行的、没有控制终端与之链接的进程。它独立与控制终端
* 方法 * - 让进程后台运行,用fork产生一个子进程,然后父进程退出
- 调用setsid创建一个新的对话期 ,让进程成为会话组长,摆脱从父进程那里继承的控制终端,登录回话,进程组
- 为了禁止重新打开控制终端,再fork一次,再让父进程退出
- 将当前目录改为根目录。当守护进程当前的工作目录再一个装配文件系统中,该文件系统不能被拆卸。
- 将文件创建时使用的屏蔽字设置为0.防止继承时没有一些权限
- 父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。父进程也不会因为等待子进程结束而增加父进程的负担
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<sys/param.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<time.h>
#include<syslog.h>
#include<stdlib.h>
int init_daemon(void)
{
int pid;
int i;
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP,SIG_IGN);
pid=fork();
if(pid>0)
{
exit(0);
}
else if(pid<0)
return -1;
setsid();
pid=fork();
if(pid>0)
exit(0);
else if(pid<0)
return -1;
for(i=0;i<NOFILE;close(i++));//关闭从父进程继承下来的不需要的文件描述符号
chdir("/");
umask(0);
signal(SIGCHLD,SIG_IGN);//忽略信号
while(1)
{
int fd=open("/home/cwh/protect.txt",O_CREAT|O_APPEND|O_RDWR,0644);//目录可以任意的改
char str[100];
strcpy(str,"just for test\n");
write(fd,str,strlen(str));
sleep(5);
}
return 0;
}
int main()
{
time_t now;
init_daemon();
}
5、进程的退出
- exit()把控制权交给系统,而return将控制权交给调用函数
- _exit()函数执行完后会立即返回给内核,而exit()要执行一些清理操作,然后才把控制权给内核
6、执行新程序
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
//代表把每个参数罗列
//execl("/usr/bin/ls", "-l", "-R", "/", (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[] */);
// char* const envp[] = {"AA=aaa", "XX=abcd", NULL};
execle("./hello", "hello", "-l", "-a", NULL, envp);
int execv(const char *path, char *const argv[]);
//char *argv[] = { "-l", "-R", "/" };execv("/usr/bin/ls", argv);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
返回值:若函数执行成功无返回值,函数执行失败则返回-1。
path:要执行的程序的路径。绝对和相对都可以。
file:要执行的程序的文件名。若指定的参数中包含“/”,则将他认为是一个路径,若不包含,则将在PATH环境变量中指定的目录下进行搜索。
arg …:在此处罗列所有传递给程序的命令行参数,最后一个参数之后要附加一个(char *) NULL来表示结束。
argv:提供一个命令行参数的指针数组,数组的最后一个字符串一定为NULL。
envp:提供一个环境变量数组。
fd:要执行的文件的文件描述符。
其他相关解释:
为何成功执行无返回值:因为exec族函数会将新的程序的代码段完全地代替子进程的代码,因此若成功执行了exec族函数,返回值已经没有意义,所以没有返回值。
执行完exec族函数之后,子进程的pid并没有发生变化,从父进程继承的各种属性也依然有效。
在以上函数中,但凡不需要指定envp[]参数的,都会将environ变量传入,当做环境变量来使用。
7、等待进程结束
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *statloc, int options);
//waitpid(pid,NULL,0)例子
返回值:若函数执行成功,则返回得到状态的进程id;若函数执行出错则在大部分情况下返回-1,在指定了一定参数时返回0。
statloc:指定为一个int指针。函数将把获取到的进程终止时的状态存储在该指针指向的区域。若将参数设置为NULL,则表示父进程不关心子进程的终止状态,将会丢弃这部分内容。
pid:指定要等待其结束的进程的pid,函数将仅在它结束的时候返回。若指定为-1,此种情况下waitpid与wait等效;若指定为0,此时将等待gid等同于调用进程组id的任一子进程;其他情况下等同于等待pid等于该参数绝对值的进程。
options:waitpid函数的选项设定。
其他相关解释
两种函数的区别:
①wait会使调用者阻塞,waitpid可以通过options参数实现不阻塞;②waitpid可以指定等待哪一个具体的进程结束。
zombie(僵死进程):当子进程终止时,不会完全地清除掉其所有状态,而是保存了至少pid、终止状态、使用的CPU时间总量等状态的信息。父进程可以通过wait或者waitpid来获取这些属性。若父进程没有处理这些信息,此时的子进程就称之为僵死进程。
信号系统相关:子进程终止时内核会向父进程发送SIGCHLD信号,父进程既可以以此来设计控制系统处理该信号,也可以选择忽略该信号(但子进程的终止状态会一直保存)。
其中,options可以设置为以下四种宏定义相互做位或运算得到的值。
选项 | 意义 |
---|---|
0 | 不设置任何特殊功能 |
WNOHANG | 若pid指定的子进程不是立即可用的,则waitpid不会阻塞,而是直接返回0 |
WCONTINUED | 若实现支持作业控制,则由pid指定的任一子进程在停止后已经继续,但其状态尚未报告,则返回其状态。 |
WUNTRACED | 若实现支持作业控制,而由pid指定的任一子进程已处于停止状态,并且其状态自停止以来还未报告过,则返回其状态。 |
其中,通过statloc参数拿到的值可以使用以下宏来检测其终止状态:
宏 | 意义 |
---|---|
WIFEXITED(statloc) | 若为正常终止的子进程返回的状态,则为真 |
WEXITSTATUS(statloc) | 获得正常终止的子进程传给exit等函数参数的低8位 |
WIFSIGNALED(statloc) | 若为异常终止的子进程返回的状态,则为真 |
WTERMSIG(statloc) | 获得使子进程异常终止的信号编号 |
WIFSTOPPED(statloc) | 若为当前暂停的子进程返回的状态,则为真 |
WSTOPSIG(statloc) | 获得使子进程暂停的信号编号 |
WIFCONTINUED(statloc) | 若在作业控制暂停后已经继续的子进程返回的状态,则为真 |
WCOREDUMP(statloc) | 若产生了终止进程的core文件,则为真 |
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
int main()
{
pid_t pid;
char *msg;
int k;
int exit_code;
printf("study how to get exit code!\n");
pid=fork();
switch(pid)
{
case 0:
msg="child process is running";
k=5;
exit_code =37;
break;
case -1:
perror("process creation failed!\n");
exit(1);
default:
exit_code=0;
break;
}
if(pid!=0)
{
int stat_val;
pid_t child_pid;
child_pid=wait(&stat_val);
printf("child process has exited,pid=%d\n",child_pid);
if(WIFEXITED(stat_val))
printf("child exited with code %d\n",WEXITSTATUS(stat_val));
else
printf("child exited abnormally\n");
}
else
{
while(k-->0)
{
puts(msg);
sleep(1);
}
}
exit(exit_code);
}
8、设置用户ID等
int setuid(uid_t uid); //设置进程实际用户ID
int setgid(gid_t gid); //设置进程实际组ID
int seteuid(uid_t uid); //设置进程有效用户ID
int setegid(gid_t gid); //设置进程有效用户组ID
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<error.h>
#include<string.h>
extern int erron;
int main()
{
int fd;
printf("uid study:\n");
printf("process's uid =%d,euid =%d\n",getuid(),geteuid());
if((fd=open("test.c",O_RDWR))==-1)
{
printf("open failture,errno is %d :%s \n",errno,strerror(errno));
exit(1);
}
else
printf("Open successfully!\n");
close(fd);
exit(0);
}
9、改变进程优先级
int nice(int incr);
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int value);
返回值:nice和getpriority若执行成功则返回新的nice值,若执行出错则返回-1;setpriority若执行成功则返回0,出错则返回-1。
incr:要增加的nice值。若incr取负数,则会减少nice值。当incr参数的取值超过指定范围,会自动将之设置为可以取到的最大值。
which:表示who属于那种类型。
who:指定要修改进程、进程组或用户的ID。 若指定为0,会根据参数解释为当前的进程ID、进程组ID或实际用户ID。
value:要改变多少。
其中,which参数可取以下值:
选项 | 意义 |
---|---|
PRIO_PROCESS | 一个特定的进程,who的值为ID |
PRIO_PGRP | 一个进程组的所有进程,who的值为进程组ID |
PRIO_USER | 一个用户拥有的所有进程,who的值为实际用户ID |