linux系统编程02-进程基本知识
1. pid
- pid_t : 进程号,一般是int_64,不同机器不同,有限资源
ps axf
:查看进程信息ps -axm
:详细信息ps -ax -L
: linux特有形式显示- 进程号是顺次向下使用,区别于 fd(占位最小)
getpid\getppid
:获取进程号\父进程号
##include <sys/types.h>
##include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
2. 进程的产生:fork
##include <sys/types.h>
##include <unistd.h>
pid_t fork(void);
- 返回值:
- 出错: -1
- 成功:返回两次。 父进程:子进程pid;子进程:0
- 注意点:fork之前刷新所有该刷新的流,
fflush(NULL)
- 父进程与子进程完全一致(duplicate) ,运行位置也一致
fork
后父子进程的区别:fork返回值不同,pid不同,ppid不同,未决信号量和文件锁不继承,资源利用量清0- 调度器的策略决定哪个进程先运行
例子:fork之前刷新所有该刷新的流
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
int main()
{
pid_t pid;
printf("[%d]:Begin.\n",getpid());
//fork前刷新所有流!!
fflush(NULL);
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0)
{
printf("[%d]:Child is working!\n", getpid());
}
else
{
printf("[%d]:Parent is working!\n", getpid());
}
printf("[%d]:End.\n", getpid());
//没有收尸,详见3.进程资源释放
exit(0);
}
运行结果:
未加fflush
加了fflush
说明:
文件是全缓冲,如果没有fork之前刷新流,子进程会继承父进程的缓冲区,所以缓冲区里有 “Begin.”,导致打印了两次。
加getchar() ps axf
查看进程信息
例子:多进程筛3000,0000 ~ 3000,0200之间的质数
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##define LEFT 30000000
##define RIGHT 30000200
int main()
{
pid_t pid;
int i,j;
int mark;
for(i = LEFT; i <= RIGHT; i++)
{
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0)
{
mark = 1;
for(j = 2; j < i/2; j++)
{
if(i%j == 0)
{
mark = 0;
break;
}
}
if(mark)
printf("%d is a primer!\n", i);
//结束子进程,非常重要
exit(0);
}
}
exit(0);
}
运行结果:
注意点:
- 子进程内必须要
exit
:否则会子进程会进入外层循环,会继续fork,导致实际创建出的子进程不是201个,而是阶乘级别的数量级。所以写fork的时候要明确子进程的运行范围,可以的话尽量限制在if的语句块中(个人观点) - 无序输出,说明调度关系未定义,取决于调度器
说明孤儿进程和僵尸进程:
父进程sleep:子进程全部结束,父进程还没有结束,所以子进程不是孤儿,并且由于资源还没有回收,所以是僵尸态
子进程sleep:父进程已经结束退出,子进程还没结束,所以子进程变成孤儿被 init 进程托管,同时资源还没被回收,所以也是僵尸进程(之后init会陆续回收资源)
进程关系中出现僵尸进程是非常正常的,但应该是一闪即逝,或者轮批次(父进程或init忙)
僵尸本身几乎就是一个结构体,所以占用的内存不是很大,但是它占用了pid
fork写时拷贝:需要改写的时候,才复制一份进行改动
3. 进程的消亡及释放资源:wait
##include <sys/types.h>
##include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
-
作用:回收某一个子进程
-
参数:
pid_t
: 要回收的子进程的pidwstatus
: 回调参数,进程结束状态options
:选项。WNOHANG
非阻塞
-
返回值:
- 成功:回收的子进程的pid
- 失败:回收完或被中断打断,-1
-
pid
的取值:
例子:利用交叉分配重构 求素数的程序
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/wait.h>
##define LEFT 30000000
##define RIGHT 30000200
##define N 3
int main()
{
pid_t pid;
int i,j,n;
int mark;
for(n = 0; n < N; n++)
{
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid > 0)
{
for(i = LEFT+n; i <= RIGHT; i+=N)
{
mark = 1;
for(j = 2; j < i/2; j++)
{
if(i%j == 0)
{
mark = 0;
break;
}
}
if(mark)
printf("[%d]:%d is a primer!\n", n,i);
//exit(0);
}
//子进程是处理完自己的部分后才结束
exit(0);
}
}
for(n = 0; n < N; n++)
wait(NULL);
exit(0);
}
运行结果:
说明:
为什么编号为0的进程没有打印,因为处理的数都是3的倍数,0,3,6,9…都是非素数。进程间的负载不均衡,可以用池化算法改进。
4. exec函数族
shell执行程序命令的方式:
- shell
fork
出一个子进程 - 子进程
exec
成待执行的程序,并执行 - shell
wait
自己的子进程结束
##include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
-
作用:用一个进程image替代当前进程image,换内不换壳,pid不变
-
参数:
- 前三个是定参,后面传的参数是要执行的程序的参数,以
NULL
结尾 - 后两个是变参,用
argv
实现,类似命令行参数
- 前三个是定参,后面传的参数是要执行的程序的参数,以
-
返回值:失败返回-1,可以不检验,直接报错(因为如果成功,进程空间就直接切出去了,不会回来)
-
注意:
fflush
-
类似
argv
[变参,arg1,arg2, … 以NULL结尾] 的结构命令行参数;
glob
;execv
例子:few 结合使用
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<unistd.h>
##include<sys/types.h>
##include<sys/wait.h>
int main()
{
pid_t pid;
puts("Begin.");
fflush(NULL); /*!!!*/
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid > 0) //子进程:打印时戳
{
execl("/bin/date", "date", "+%s", NULL);
//错误处理
perror("exec()");
exit(1);
}
else //父进程:回收子进程
{
wait(NULL);
}
exit(0);
}
运行结果:
- 其他问题:
-
没有wait的时候为什么命令行先输出:父进程结束后子进程没结束,父进程结束回到bash,打印命令行,然后子进程打印结果
-
为什么父子进程的结果打印在同一个终端上,父子进程通信原型:子进程继承父进程的文件描述符,二者 fd0标准输出都一样,都是同一个终端
-
综合例子:mybash
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<unistd.h>
##include<string.h>
##include<glob.h>
##include<sys/wait.h>
##define LIMITS " \n\t"
struct cmd_st
{
glob_t globres;
};
static void prompt()
{
printf("mysh-0.1$ ");
}
static void perse(char *line, struct cmd_st *res)
{
char *tok;
int i = 0;
while(1)
{
tok = strsep(&line, LIMITS);
if(tok == NULL) //结束
break;
if(tok[0] == '\0') //多个连续分隔符
continue;
//精髓:
glob(tok, GLOB_NOCHECK|GLOB_APPEND*i, NULL, &res->globres);
i = 1;
}
}
int main()
{
char *linebuf = NULL;
size_t linebuf_size = 0;
struct cmd_st cmd;
size_t pid;
while(1)
{
prompt(); //打印提示信息
if(getline(&linebuf, &linebuf_size, stdin)<0)
break;
perse(linebuf, &cmd);
if(0) //内部命令
{ }
else //外部命令
{
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0) //child
{
execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);
perror("execl()");
exit(1);
}
else //parent
{
wait(NULL);
}
}
}
exit(0);
}
运行结果:
说明:
-
利用 glob_t 中的 gl_pathv 传递变参
-
strtok 的用法
使用:
5. 用户权限和组权限:setuid
##include <sys/types.h>
##include <unistd.h>
int setuid(uid_t uid); //设置用户 effective id
例子:实现 mysu
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/wait.h>
int main(int argc, char **argv)
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0)
{
setuid(atoi(argv[1]));
execvp(argv[2], argv+2);
perror("exec()");
exit(1);
}
wait(NULL);
exit(0);
}
运行结果:
- 切换到root用户
- 把
mysu
的所有者更改为root,并添加 u+s 权限
基础知识:
- 权限确定:
res
:real,effective,save。save几乎不用,effective用来确定身份。 - 什么是
u+s
/g+s
权限:执行该文件时,暂时获得其所有者的权限。比如passwd
命令有u+s
所以在执行的时候暂时获得其所有者root的权限,这就是为什么普通用户无法查看 /etc/shadow,却可以通过passwd
命令更改该文件。u+s
和g+s
本质是root权限的下放。
- shell如何获取身份:init[root身份] —fork\exec—>getey(name)—fork\exec—>login(pw)—成功—>shell[用户身份 res]
6. 观摩课
unix讲机制,不讲策略
对于 #!/bin/bash
shell看到 #!
知道它是一个脚本,所以不装载该程序,而是装载 /bin/bash
执行下面的语句。
如果改为 #!/bin/bash
就会出现下述情况:
把top命令做成了登录shell:
7. system
理解: fork() + exec() + wait()
的封装。
##include <stdlib.h>
int system(const char *command);
system(”date +%s”)
相当于
8. 进程会计
acct()函数
:仅作为了解
9. 进程时间
可以转换成秒了 time times
10.守护进程
进程组模型:
-
session是一些进程组的集合:每一个登录的shell是一个session,有自己的sid。
-
分组都是为了方便管理。用户有用户组,进程也有进程组,有pgid。父子进程在同一个进程组,执行
ls|more
的时候,进程ls
和 进程more
也是在同一个进程组。 -
进程是一个容器,包含许多线程
-
前后台进程:前台进程关联标准io,最多有一个。后台进程不关联标准io
守护进程:
- 是进程组的leader,是session的leader
- 脱离标准io
- 体现在终端上
ps axj
: pid = pgid = sid; tty = ? ;ppid = 1
##include <sys/types.h>
##include <unistd.h>
pid_t setsid(void);
- 使用:不能由父进程执行,只能由子进程
- 流程:
-
创建一个session及进程组,调用该函数的进程编程该session和进程组的leader
-
脱离控制终端
-
例子:创建守护进程,关注 fork
的一模一样
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/stat.h>
##include<fcntl.h>
##define FILENAME "/tmp/out"
//创建守护进程:成功返回非0值
static int daemonize()
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
perror("fork()");
return -1;
}
if(pid > 0) //父进程直接退出
exit(0);
//子进程
fd = open("/dev/null", O_RDWR);
if(fd < 0)
{
perror("open()");
return -1;
}
//文件重定向
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2)
close(fd);
//创建session
setsid();
//更改工作目录;(修改umask)
chdir("/");
//umask(0);
return 0;
}
int main()
{
FILE *fp;
int i;
if(daemonize())
exit(1);
fp = fopen(FILENAME, "w");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}
for(i = 0; ; i++)
{
fprintf(fp, "%d\n", i);
fflush(NULL); //文件是全缓冲
sleep(1);
}
fclose(fp);
exit(0);
}
运行结果:
分析: daemon
中创建了子进程,父进程终止,子进程执行 setid
,化身为守护进程,然后从父进程相同的位置继续执行,退出到main,执行死循环。
问题:错误都往标准输出上报,但是守护进程已经脱离标准输出了,用系统日志
11. 系统日志
权限分离
syslogd服务
##include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
示例代码:
##include<stdio.h>
##include<stdlib.h>
##include<sys/types.h>
##include<unistd.h>
##include<sys/stat.h>
##include<fcntl.h>
##include<syslog.h>
##include<errno.h>
##include<string.h>
##define FILENAME "/tmp/out"
//创建守护进程:成功返回非0值
static int daemonize()
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
return -1;
}
if(pid > 0) //父进程直接退出
exit(0);
//子进程
fd = open("/dev/null", O_RDWR);
if(fd < 0)
{
return -1;
}
//文件重定向
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2)
close(fd);
//创建session
setsid();
//更改工作目录;(修改umask)
chdir("/");
//umask(0);
return 0;
}
int main()
{
FILE *fp;
int i;
openlog("mydaemon", LOG_PID, LOG_DAEMON);
if(daemonize())
{
syslog(LOG_ERR, "daemonize failed!");
exit(1);
}
else
{
syslog(LOG_INFO, "daemonize success!");
}
fp = fopen(FILENAME, "w");
if(fp == NULL)
{
syslog(LOG_ERR, "fopen():%s", strerror(errno));
exit(1);
}
syslog(LOG_INFO, "%s was open", FILENAME);
for(i = 0; ; i++)
{
fprintf(fp, "%d\n", i);
fflush(NULL); //文件是全缓冲
sleep(1);
}
fclose(fp);
exit(0);
}
运行结果:
su root
tail -f /var/log/syslog