一、昨日Review
1、如何区分两个进程?使用pid
2、如何获取pid和父亲的pid?使用getpid和getppid
3、getuid/getgid/geteuid/getigid
4、内核判断进程能做什么事情时,进程检查的是有效用户id
5、什么是孤儿进程?父进程先于子进程结束,子进程被1号进程接管
6、什么是僵尸进程?子进程先退出,但父进程没有对子进程做清理工作,则已退出的子进程就变成了僵尸进程。
7、进程终止的5种方式?main函数的自然返回、调用exit函数、调用_exit函数、调用abort函数、接收到能导致进程终止的信号ctrl+c SIGINT或者ctrl+\SIGQUIT
二、今日内容
1、什么是守护进程?
(1)守护进程就是daemon进程,是没有控制终端与之相连的进程,它独立执行某种任务。
(2)为什么要脱离终端控制?因为终端被关闭时,所有该终端启动的进程都会被自动关闭,守护进程可以突破这种现实,它被执行开始运转,直到整个系统关闭时才退出。
(3)一般在企业中,服务器应用都是作为守护进程来运行的。
(4)如何判断一个进程是不是守护进程?在使用ps命令查询时,凡是进程后缀以D结尾的,都是守护进程
(5)daemon进程,为了使当前目录所在的文件不能卸载,所以利用chdir("/"),把当前工作目录切换到根目录。
(6)因为守护进程不需要标注输入、标准输出、标准错误输出,所以守护进程需要关闭三个端口,使用代码
for(i=0;i<MAXFILE;i++)
close(i);
(7)代码例子daemon.c
#include <func.h>
void Daemon()
{
if(fork())
{
exit(0);
}
setsid(); //重设会话组
chdir("/"); //切换到根目录
umask(0);
int i;
for(i=0;i<3;i++)
{
close(i);
}
}
int main(int argc,char* argv[])
{
Daemon();
while(1); //使用了while(1)后,如果使用ctrl+c结束进程,实际上进程是不会终止的,进程仍然在后台继续运行
return 0;
}
2、
①获取进程组id是getpgid
②创建进程组id是setpgid
③获取会话getsid
④进程组长的pid就是pgid号
3、代码例子getpgid.c
#include <func.h>
int main(int argc,char* argv[])
{
if(!fork())
{
printf("I am child pid=%d ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
setpgid(0,0);//成立新的进程组
printf("I am child pid=%d ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
while(1);
return 0;
}else{
printf("I am parent pid=%d ppid=%d,pgid=%d\n",getpid(),getppid(),getpgid(0));
wait(NULL);
return 0;
}
}
经过本例,可以验证组id就是组长的pid
4、Linux中如何获取时间
(1)一些命令
①time_t 秒数
②ctime 字符串时间
③gmtime 格林尼治
(2)代码例子gemtime.c
#include <func.h>
int main(int argc,char* argv[])
{
time_t now;
time(&now);
struct tm *p;
p=gmtime(&now);
printf("%04d-%02d-%02d %02d:%02d:%02d %d %d\n",p->tm_year+1900,p->tm_mon+1,p->tm_mday,\
p->tm_hour+8,p->tm_min,p->tm_sec,p->tm_wday,p->tm_yday);
return 0;
}
5、管道
(1)管道的分类:标准流管道、无名管道、命名管道
(2)标准流管道实际是使用无名管道实现的
(3)管道主要使用函数popen和pclose
(4)管道关闭要使用pclose
6、文件夹popen中
(1)代码例子print.c
#include <func.h>
int main(int argc,char* argv[])
{
printf("I am print\n");
return 0;
}
(2)代码例子popen_r.c
#include <func.h>
int main(int argc,char* argv[])
{
FILE *fp;
fp=popen("ls","r");
ERROR_CHECK(fp,NULL,"popen");
char buf[128];
fread(buf,sizeof(char),sizeof(buf)-1,fp);
printf("buf=%s\n",buf);
pclose(fp);
return 0;
}
(3)代码例子add.c
#include <func.h>
int main(int argc,char* argv[])
{
int i,j;
scanf("%d%d",&i,&j);
printf("sum=%d\n",i+j);
return 0;
}
(4)代码例子popen_w.c
#include <func.h>
int main(int argc,char* argv[])
{
FILE *fp;
fp=popen("./add","w");
ERROR_CHECK(fp,NULL,"popen");
char buf[128]="3 4";
fputs(buf,fp);
pclose(fp);
return 0;
}
(5)编译文件时,如果想要生成的文件叫做其他名字,可以使用命令gcc -o [想要生成的文件名] [.c文件名]
例如:gcc -o test add.c
13、文件夹pipe中
代码例子pipe.c
#include <func.h>
int main(int argc,char* argv[])
{
int fds[2];
pipe(fds);//管道的读端就存在fds[0],写端存在fds[1]
if(!fork())
{
close(fds[1]);//关闭写端因为子进程要读
char buf[128]={0};
read(fds[0],buf,sizeof(buf));
printf("I am child,gets=%s\n",buf);
exit(0);
}else{
close(fds[0]);//关闭读端,因为父进程要写
write(fds[1],"I hen niu",9);
wait(NULL);
}
return 0;
}
因为管道是半双工,所以进程间使用管道实现双向通信,需要使用两个管道
7、代码示例unlink.c
#include <func.h>
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
int ret;
ret=unlink(argv[1]);
ERROR_CHECK(ret,-1,"unlink");
return 0;
}
①删除fifo文件使用unlink命令
8、实例代码ftok.c
#include <func.h>
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
key_t key;
key=ftok(argv[1],1);
printf("key=%d\n",key);
return 0;
}
9、创建一个父子进程,让他们通过共享内存,直接通信
代码例子fork_shm.c
#include <func.h>
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int *p;
p=(int*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(int*)-1,"shmat");
if(!fork())
{
printf("I am child %d,%p\n",*p,p);
}else{
p[0]=10;
printf("I am parent %d,%p\n",*p,p);
wait(NULL);
}
return 0;
}
①怎么看共享内存?使用命令ipcs
10、创建一个父子进程,让父子进程同时对数据进行加法,各加1000万,那结果是2000万吗?
代码例子add1000.c
#include <func.h>
#define N 10000000
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int *p;
p=(int*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(int*)-1,"shmat");
*p=0;
if(!fork())
{
int i;
for(i=0;i<N;i++)
{
p[0]=p[0]+1;
}
}else{
int i;
for(i=0;i<N;i++)
{
p[0]=p[0]+1;
}
wait(NULL);
printf("result=%d\n",p[0]);
}
return 0;
}
(1)Wait(NULL),父进程必须回收子进程的资源,所以在执行到这一句时,父进程和子进程都已经执行完毕了
(2)
①为什么最终的结果不等于2000万?这是遇到的第一个并发问题
②如何解决并发问题?使用负载均衡,降低并发量。
③为什么会不等于2000万?改成双核后,并发度更高,所以值会更小
④如何让他等于2000万?需要加保护
⑤资源保护的方法有?让大家轮流操作。
⑥资源保护需要用到信号量
11、解除链接使用函数shmdt,将共享内存段与进程空间分离。
代码示例shmdt.c,就可以解除链接
#include <func.h>
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
char *p;
p=(char*)shmat(shmid,NULL,0);
ERROR_CHECK(p,(char*)-1,"shmat");
strcpy(p,"hello");
int ret;
sleep(10);
ret=shmdt(p);
ERROR_CHECK(ret,-1,"shmdt");
while(1);
return 0;
12、函数shmctl是共享内存的控制函数,可以用来删除共享内存段
(1)加入有人正在使用共享内存,可以删除吗?不可以
(2)代码例子 shmctl_rmid.c
#include <func.h>
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int ret;
ret=shmctl(shmid,IPC_RMID,NULL);
ERROR_CHECK(ret,-1,"shmctl");
return 0;
}
(3)代码例子shmctl_stat.c
#include <func.h>
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int ret;
struct shmid_ds buf;
ret=shmctl(shmid,IPC_STAT,&buf);
ERROR_CHECK(ret,-1,"shmctl");
printf("%d %o %ld %ld\n",buf.shm_perm.uid,buf.shm_perm.mode,buf.shm_segsz,buf.shm_nattch);
return 0;
}
(4)代码例子shmctl_set.c
#include <func.h>
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<20,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int ret;
struct shmid_ds buf;
ret=shmctl(shmid,IPC_STAT,&buf);
ERROR_CHECK(ret,-1,"shmctl");
printf("%d %o %ld %ld\n",buf.shm_perm.uid,buf.shm_perm.mode,buf.shm_segsz,buf.shm_nattch);
buf.shm_perm.mode=0666;
shmctl(shmid,IPC_SET,&buf);
return 0;
}
①命令ipcs查看共享内存
②命令ipcrm -m shmid删除共享内存
13、虚拟地址和物理地址的转换
(1)问题:
①不同进程相同的虚拟地址,是否可以映射不同的物理地址?
可以,每个进程都有自己的段基址寄存器。
②不同进程相同的虚拟地址,是否可以映射到相同的物理地址?
可以,共享内存
③不同进程不同的虚拟地址,是否可以映射到相同的物理地址?
可以,通过mmap可以实现共享没存
(2)转换过程
(3)新的用法
①怎样提高虚拟地址->物理地址的速度?使用快表,但是快表中存放的都是高频数据,因为快表的容量有限
②怎么降低TLB miss?使用大页
③什么地方使用大页hugepages?大页=2M;普通页=4K
④用K值创建共共享内存,再让其他人使用K值调用共享内存,这种设计有什么好处?便于分享
(4)私有方式创建共享内存
代码例子shmget_rpivate.c
#include <func.h>
#define SHM_HUGE_2MB (1<<21)
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<21,IPC_CREAT|0600|SHM_HUGETLB|SHM_HUGE_2MB);
ERROR_CHECK(shmid,-1,"shmget");
return 0;
}
(5)怎么使用大页?
代码例子shmget_hugepage.c
#include <func.h>
#define SHM_HUGE_2MB (1<<21)
int main(int argc,char* argv[])
{
int shmid;
shmid=shmget(1000,1<<21,IPC_CREAT|0600|SHM_HUGETLB|SHM_HUGE_2MB);
ERROR_CHECK(shmid,-1,"shmget");
return 0;
}
(6)小问题:
①proc是什么?/proc是内存文件系统,是存在内存中的内容,一旦关机,内容就被清空。里边每个文件夹都是一个进程,文件夹名就是一个pid
②如何查看内核版本?使用命令uname -a
③sudo只是权限提升, 和直接使用su还是有区别的
④使用命令cat /proc/meminfo,可以查看当前大页的使用情况
⑤SWAP分区是物理内存,名字叫做虚拟内存。
(7)使用mmap实现共享内存
代码示例mmap_sharememory.c
#include <func.h>
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
int fd=open(argv[1],O_RDWR);
ERROR_CHECK(fd,-1,"open");
char *p=(char*)mmap(NULL,10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
ERROR_CHECK(p,(char*)-1,"mmap");
if(!fork())
{
printf("I am child %s\n",p);
}else{
strcpy(p,"hello");
wait(NULL);
}
return 0;
}
(8)MS_ASYNC和MS_SYNC,用于将内存中的数据同步到磁盘中
MS_ASYNC:
MS_SYNC:
(9)mmap实现大页
代码例子mmap_hugepage.c
#include <func.h>
//要把文件系统挂载为大页形式的文件系统
#define MAP_HUGE_2MB 1<<21
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,2);
int fd=open(argv[1],O_RDWR|O_CREAT,0666);
ERROR_CHECK(fd,-1,"open");
int ret;
char *p=(char*)mmap(NULL,MAP_HUGE_2MB,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
ERROR_CHECK(p,(char*)-1,"mmap");
strcpy(p,"hello");
sleep(20);
ret=munmap(p,MAP_HUGE_2MB);
ERROR_CHECK(ret,-1,"munmap");
return 0;
}
①任何目录都必须挂载一下,使用命令
mount none /mnt/huge -t hugetlbfs
来自 <https://www.ibm.com/developerworks/cn/linux/l-cn-hugetlb/index.html>
②在执行mmap_hugepage时,要先把系统挂载成大页形式,使用上面的命令
③怎么更改大页数量?Echo 20 > /proc/sys/vm/nr_hugepages
④SWAP分区不可以设置太小
⑤使用命令mlock可以将内存锁住,这样程序就不会被交换到SWAP空间了
14、使用信号量,实现1000万+1000万=2000万
(1)使用信号量,需要往头文件里增加#include<sys/sem.h>
(2)实例代码semget.c
#include <func.h>
int main(int argc,char* argv[])
{
int semArrId=semget(1000,1,IPC_CREAT|0600);
ERROR_CHECK(semArrId,-1,"semget");
return 0;
}
(3)semget里可以创建多把锁
(4)第二段代码在sem_add1000.c文件中,再增加锁的相关操作
(5)锁应该加到for循环里边