课后简单题以及程序设计题
简单说明软连接和硬链接文件的区别
- 创建链接文件的命令为ln,该命令默认创建硬链接文件,若与选项-n搭配使用,则会创建软链接文件。软链接文件是一个新文件,系统会为软链接文件分配inode;软链接文件中存储的内容为其所链接对象的路径。硬链接文件与其所链接的文件共用inode,创建硬链接文件时,该文件的文件名作为一条记录添加到其路径名中目录的dentry中,同时其链接对象的硬链接数会加1。
程序与进程的区别
- 、程序是“死”的,进程是“活”的,程序是指编译好的二进制文件,它存放在磁盘上,不占用系统资源,是具体的;而进程存在于内存中,占用系统资源,是抽象的。当一次程序执行结束之后,进程随之消失,进程所用的资源被系统回收。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if (pid == -1)
{
perror("fork error");
exit(1);
}
else if (pid > 0)
{
printf("parent process:pid");
}
else if (pid == 0)
{
printf("child process\n");
execlp("cat", "-b", "exec.c", NULL);
perror("error exec\n");
printf("child process\n");
}
return 0;
}
简单说明linux系统信号的处理方式
信号递达进程后才可能被处理,信号的处理方式有三种:忽略、捕捉和执行默认动作。大多数信号都可以被忽略,但9号信号SIGKILL和19号信号SIGSTOP是超级用户杀死进程的可靠方法,不能被忽略,也不能被捕获。
说明kill函数pid不同取值的含义
参数pid的取值可分为4种情况,每种取值代表的含义如下:①pid > 0,则发送信号sig给进程号为pid的进程;②pid = 0,则发送信号sig给当前进程所属组中的所有进程;③pid = -1,则发送信号sig给除1号进程与当前进程的所有进程;④pid < -1,则发送信号sig给属于进程组-pid的所有进程。
简述使用消息队列实现进程间通信的步骤
使用消息队列实现进程间通信的步骤如下:①创建消息队列;②发送消息到消息队列;③从消息队列中读取数据;④删除消息队列。
列举出Linux,中常用的进程通信机制,并对每种机制进行简单说明
Linux系统中常用的进程通信机制有:管道、消息队列、信号量和共享内存。其中管道分为匿名管道和命名管道,匿名管道可用于具有亲缘关系进程间的通信,命名管道克服了此限制,可实现任意进程间的通信。消息队列的本质是一个存储消息的链表,有权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读取队列中的消息,消息队列也可实现任意进程间的通信,且克服了管道只能承载无格式字节流以及缓冲区大小受限等缺点。共享内存机制将同一块物理地址映射到不同进程的虚拟地址空间,使得多个进程可访问同一块内存空间,效率最高。信号量主要用于实现进程同步,常常与共享内存一起使用,避免多个进程因争夺共享资源出现错误。
简述线程和进程的区别
1、进程有自己独立的地址空间,多个线程共享进程的地址空间;进程是分配资源的基本单位,线程是处理器调度的基本单位;进程是程序的一次执行过程,线程是进程的一个执行单元;进程和线程均可并发执行,但线程占据的空间更小,耗费的资源更少。
su与sudo的区别
综合编程题
- 所有的系统函数都应该检查返回值,设置error函数
int linux1()
{ int i,fd,result;
char buf[1000];
close(1);
fd=open("nfile",O_RDWR|O_CREAT,0644);
//lseek(fd,1000,SEEK_SET);
if(fd<0)
{ perror("open error\n");
return -1;
}
lseek(fd,1000,SEEK_SET);
write(fd," ",1);
char n='a';
for(i=0;i<26;i++)
{ printf(n+" ");
n++;
sleep(1);
}
//write(fd,n,1);
write(fd,buf,1000);
close(fd);
return 0;}
文件系统操作
一、作业
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
//相对路径(在同一文件目录下)
int main(){
int fd=open("a.txt",O_RDWR);
if(fd==-1){//注意这里是两个等于号!!!!!!
perror("file open error\n");
exit(1);
}
else
printf("fd=%d",fd);
}
//命令行参数
int main(int argc,char *argv[]){
int fd=open(argv[1],O_RDWR);
if(fd==-1){
perror("file opne error\n");
}
else
printf("fd=%d",fd);
//绝对路径,不再同一文件目录下
int main(){
int fd=open("/home/kali/Desktop/test/b.txt",O_RDWR);
if(fd==-1){
perror("file open error\n");
exit(1);
} else
printf("fd=%d",fd);
cp
lseek
- 返回值是以起始位置为标准向后的偏移程度。
- 文件的读写使用同一偏移量。
- 写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。lseek读取的文件大小总是相对文件头部而言。
- 用lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799,ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号.末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。
- 使用 truncate 函数,直接拓展文件。 int ret = truncate(“dict.cp”, 250);
lseek 示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾
代码如下:
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
int fd,n;
char msg[]={"it's atest for lseek"};
char ch;
fd=open("lseek.txt",O_RDWR);
if(fd==-1){
perror("open error");
exit(1);
}
write(fd,msg,strlen(msg));//将参数msg所指的内存写msg数组的长度到文件fd中
lseek(fd,0,SEEK_SET);
/*while((n = read(fd, &ch, 1))){
if(n < 0){
perror("read error");
exit(1);
}
write(STDOUT_FILENO, &ch, n);
}//将文件内容按字节读出,写出到屏幕 }
close(fd);
return 0;
}*/
len=lseek(fd,0,SEEK_END);//获取文件长度;
while(lseek(fd,0,SEEK_CUR)!=len)
{
read(fd,msgy7,1024);
printf("%s\n",msg);
}
close(fd);
return 0;
}
- “open函数里的文件名或路径名都要加引号!”。
拓展文件的大小
下面使用lseek将fcntl.c原本的698字节填充为800字节,差值102字节.
权限
- 例如:输入umask命令后,得出的是0002,umask是八进制,真正的值为002。
- umask ,mode=775,777,结果为775。
- umsk,mode=511,775,结果为511
- 两者相与。
open().close().write().read()
read
- 函数定义:ssize_t read(int fd, void * buf, size_t count)
- 函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。
- 返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。另外,以下情况返回值小于count.
- 读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有50个字节而请求读100个字节,则read返回50,下次read将返回0。
write
- 函数定义:ssize_t write (int fd, const void * buf, size_t count);
- 函数说明:write()会把参数buf所指的内存写入count个字节到所指的文件fd内。
- 返回值:如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
- write()函数返回值一般无0,只有当如下情况发生时才会返回0:write(fd, buf, count)中第三参数为0,此时write()什么也不做,只返回0。
inode
进程
- ps aux 返回结果里,第二列是进程 id
- ps ajx --> pid ppid gid sid
- exec函数族,,,函数通常用来调用系统程序。如 ls、date、cp、cat 命令。
- exec函数一旦调用成功,即执行新的程序,不返回。只有失败才返回,错误值-1,所以通常我们
直接在 exec 函数调用后直接调用 perror(), 和 exit(),无需 if 判断。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();//调用fork创建子进程
if (pid == -1) {
perror("fork error");
exit(1);
}
else if(pid>0) {
printf("parents process,pid=%d,ppid=%d\n",getpid(),getppid());
}
else if (pid == 0) {
printf("child process,pid=%d.ppid=%d",getpid(),getppid());
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#icnlude < unidtd.h>
int main() {
pid_t pid;
int i;
for ( i = 0; i < 5; i++) {
pid = fork();
if(pid==0)
break;
}
if (pid == -1) {
perror("fork error");
exit(1);
}
else if (pid > 0) {
printf("parent process:pid=%d\n", getpid());
}
else if (pid == 0) {
printf("i am child=%d,pid=%d\n",i+1,getpid());
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();//调用fork创建子进程
if (pid == -1) {
perror("fork error");
exit(1);
}
else if(pid>0) {
printf("parents process,pid=%d,ppid=%d\n",getpid(),getppid());
}
else if (pid == 0) {
printf("child process,pid=%d.ppid=%d",getpid(),getppid());
char *arg[]={"-a","-l","test_work.c",NULL};//定义指针数组要加星号
execvp("ls",arg);
perror("error exrc\n");
}
return 0;
}
孤儿进程与僵尸进程
- 孤儿进程
- 父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。
- 僵尸进程
- 子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。
- kill 对其无效。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。
子进程终止时,子进程残留资源 PCB 存放于内核中,PCB 记录了进程结束原因,进程回收就是回
收 PCB。回收僵尸进程,得 kill 它的父进程,让孤儿院去回收它。
- 僵尸进程会占据内存空间,要尽量避免僵尸进程的产生,使用wait和waitpid()可以有效避免僵尸进程
- 若僵尸进程已产生,解决僵尸进程的办法就是终止其父进程,僵尸进程的父进程被终止后,就会变成孤儿进程,会被init接受,init会不断调用wait函数获取子进程状态,收集已退出的子进程发送的状态信息。孤儿进程永远不会变为僵尸进程。
孤儿进程
- 孤儿进程的创建
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
int main()
{
pid_t pid = fork();
if(pid == 0)//子进程
{
sleep(1);//目的是让父进程先于子进程执行完毕
printf("son process start\n");
printf("my pid is %d,my ppid is %d\n",getpid(),getppid());
printf("son process end\n");
}
else
{
printf("father process start\n");
printf("my pid is %d\n",getpid());
printf("father process end\n");
}
return 0;
}
- 孤儿进程的解决
僵尸进程
- 僵尸进程的创建
子进程先于父进程退出,且父进程没有对子进程资源进行回收
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t pid = fork();//创建子进程
if(pid == 0)
{
printf("my pid is %d\n",getpid());
printf("my ppid is %d\n",getppid());
}
else
{
while(1)
{
;//死循环,阻塞 不处理已经退出的子进程,致使子进程称为僵尸进程
}
}
return 0;
}
**
- 僵尸进程的解决
- 子进程后于父进程退出,变为孤儿进程,有init进程接管子进程,释放其资源
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
pid_t res = fork();
if (res == -1)
{
printf("Creat process failed!\n");
return -1;
}
else if (res == 0)
{
printf("I'm child process,PID = %d,PPID = %d\n", getpid(), getppid());
while (1)
{
sleep(1);
}
}
else
{
printf("I'm father process,PID = %d,PPID = %d\n", getpid(), getppid());
}
exit(0);
}
- wait()函数
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(0 == pid)
{
printf("my pid is %d\n",getpid());
}
else
{
sleep(1);//目的是让子进程先于父进程结束
/*wait函数是阻塞函数,会等到子进程结束回收资源*/
pid_t dpid = wait(NULL);//对子进程的退出状态不关心
printf("the died son process pid is %d\n",dpid);
}
return 0;
}
1.获取正常的退出状态
#include<sys/types/h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pid = fork();
if(0 == pid)
{
printf("my pid is %d\n",getpid());
}
else
{
sleep(1);
int status = 0;//初始化为0
pid_t dpid = wait(&status);
if(WIFEXITED(status))
{
printf("exit value:%d\n",WEXITSTATUS(status));
}
printf("the died son process pid is %d\n",dpid);
}
return 10;//返回值为10
}
2.获取异常的退出状态
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pd = fork();
if(pid == 0)
{
while(1)
{
printf("my pid is %d\n",getpid());
sleep(2);//控制执行速度不要太快
}
else
{
sleep(1);
int status = 0;//初始化为0
wait(&status);
if(WIFSIGNALED(status))
{
printf("exit by signal : %d\n",WTEEMSIG(status));
}
}
}
return 0;
}
wait(),waitpid():回收内核空间资源,8
等待子进程结束
- 调用wait()函数的父进程,如果其所有的子进程都还在运行,则父进程会阻塞。并阻塞等待任意一个子进程结束,回收该子进程的内核资源,如果父进程没有任何子进程,则会出错。
- waitpid()函数并不等待其在调用之后的第一个终止子进程,他又若干个选项,可以控制它所等待的进程。
waitpid()的第三个参数为等待选项,可以设置为0,亦可为WNOHANG,(不阻塞等待),当此时没有进程退出,即返回0,而不会使阻塞。WUNTRACED:报告状态信息。
- wait()是随机回收子进程,waitpid是指定回收子进程。
信号
- 信号抵达进程后才可能被处理,信号的处理方式有三种,忽略,捕获和执行默认动作。
-
在程序中设置计时器,使进程在指定秒数后终止运行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
alarm(1);
while(1){
printf("process will finish\n");
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(){
pid_t pid;
pid=fork();
if(pid<-1){
perror("fork error");
exit(1);
}
else if(pid>0){
while(1){
printf("parent pid =%d,ppid=%d\n",getpid(),getppid());
}
}
else if(pid==0){
sleep(1);
printf("child pid=%d,ppid=%d\\n",getpid(),getppid());
kill(getppid(),SIGKILL);
}
return 0;
}
signal
- 注册一个信号捕捉函数
sigaction函数
- 注册一个信号捕捉函数,捕捉不是由这个函数完成的!
进程间通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#incldue <sys/types.h>
#incldue <sys/wait.h>
int main(){
int fd[2];
int ret=pipe(fd);
if(ret==-1){
perror("pipe");
exit(1);
}
pid_t pid=fork();
if(pid>0){
close(fd[0]);
char *p="hello\n";
write(fd[1],p,strlen(p)+1);
close(fd[1]);
wait(NULL);
}
else if(pid==0){
close(fd[1]);
char buf[64]={0};
ret=read(fd[0],buf,sizeof(buf));
close(fd[0]);
write(STDOUT_FILENO,buf,ret);
}
return 0;
}
mmap
线程
线程概述:
线程是最小的执行资源单位,进程是最小的分配资源单位。
创建线程
当主线程创建好后,tfn函数会自动被回调
程序改进:加sleep(1),给子线程执行时间
#include <stido.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg){
printf("tfn-pid=%d,tid=%lu\n",getpid(),pthread_self());
return (void *)0;
}
int main(){
pthread_t tid;
printf("main--pid=%d,tid=%lu\n",getpid(),pthread_self());
int ret=pthread_create(&tid,NULL,tfn,NULL);
tid要用取地址运算符
if(ret!=0){
fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
exit(1);
}
sleep(1);
return 0;
}
线程与共享
主子线程间共享全局变量,即线程间共享全局变量
- 进程:
进程拥有独立的地址空间,当使用fork()函数创建出新进程后,若其中一个进程要对fork()之前的数据进行修改,进程中会依据“写时复制”原则,先复制一份该数据到子进程的地址空间,再修改数据,因此即便是全局变量,在进程间也是不共享的。
- 线程
但由于线程间共享地址空间,因此在一个线程中对全局区的数据进行修改,其它线程中访问到的也是修改后的数据。
线程终止
- 在一个时间段内,程序被挂起时,可以被取消的一个时间点。(APUE363页有详细的取消点函数)
也就是说,当线程出现 block(阻塞) 时,这个被阻塞的地方就是可被取消的地方。
更通俗的来说:就是线程A执行过程中,如果遇到其他线程B执行cancel函数,
- 所谓“取消点”就是一个函数,例如sleep(),当线程体运行到sleep()的时候,就可以响应cancel了。